mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 01:45:13 +08:00
🎁 Sync 2026-07-16 21:58:33
This commit is contained in:
1 parent
257116fcb5
commit
b623db8914
46 files changed
+490
-615
No files matched your search
+2
-2
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ddns-go
|
||||
PKG_VERSION:=6.17.1
|
||||
PKG_RELEASE:=18
|
||||
PKG_VERSION:=6.17.2
|
||||
PKG_RELEASE:=19
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.7.15
|
||||
PKG_RELEASE:=180
|
||||
PKG_VERSION:=26.7.16
|
||||
PKG_RELEASE:=181
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
function is_json(str) {
|
||||
try {
|
||||
const result = JSON.parse(str);
|
||||
return typeof result === 'object' && result !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function is_timehhmm(str) {
|
||||
const match = String(str).match(/^(\d?\d):(\d\d)$/);
|
||||
if (match) {
|
||||
const hour = parseInt(match[1], 10);
|
||||
const minute = parseInt(match[2], 10);
|
||||
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) {
|
||||
cbi_validators['json'] = function() {
|
||||
return is_json(this);
|
||||
}
|
||||
cbi_validators['timehhmm'] = function() {
|
||||
return is_timehhmm(this);
|
||||
}
|
||||
} else {
|
||||
L.require('validation').then(function(validation) {
|
||||
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)'));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1152,10 +1152,20 @@ function fetch_certsha256()
|
||||
local port = (id ~= "") and uci:get(appname, id, "port") or 0
|
||||
local sni = (id ~= "") and uci:get(appname, id, "tls_serverName") or ""
|
||||
sni = (sni ~= "") and sni or address
|
||||
local protocol = uci:get(appname, id, "protocol")
|
||||
local h3, timeout = false, 10
|
||||
if protocol == "hysteria2" then
|
||||
h3 = true
|
||||
timeout = 60
|
||||
if port == 0 then
|
||||
local hop = uci:get(appname, id, "hysteria2_hop") or "0"
|
||||
port = tonumber(hop:match("^%s*(%d+)"))
|
||||
end
|
||||
end
|
||||
if address == "" or port == 0 then
|
||||
http_write_json_error()
|
||||
return
|
||||
end
|
||||
local data = api.fetch_cert_sha256(address, port, sni, 5)
|
||||
local data = api.fetch_cert_sha256(address, port, sni, timeout, h3)
|
||||
http_write_json(data ~= "" and { code = 1, data = data } or { code = 0 })
|
||||
end
|
||||
@@ -2,6 +2,8 @@ local api = require "luci.passwall.api"
|
||||
local appname = "passwall"
|
||||
local sys = api.sys
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@@ -13,7 +15,8 @@ o.rmempty = false
|
||||
o.default = false
|
||||
|
||||
-- [[ ACLs Settings ]]--
|
||||
s = m:section(TypedSection, "acl_rule")
|
||||
local cfgname = "acl_rule"
|
||||
s = m:section(TypedSection, cfgname)
|
||||
s.template = "cbi/tblsection"
|
||||
s.sortable = true
|
||||
s.anonymous = true
|
||||
@@ -101,4 +104,9 @@ o:value("1:65535", translate("All"))
|
||||
o:value("53", "53")
|
||||
]]--
|
||||
|
||||
return m
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
return api.return_map(m)
|
||||
@@ -1,6 +1,8 @@
|
||||
local api = require "luci.passwall.api"
|
||||
local appname = "passwall"
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
m.redirect = api.url("acl")
|
||||
api.set_apply_on_parse(m)
|
||||
@@ -601,4 +603,4 @@ for k, v in pairs(nodes_table) do
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -2,6 +2,8 @@ local api = require "luci.passwall.api"
|
||||
local com = require "luci.passwall.com"
|
||||
local appname = "passwall"
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@@ -34,4 +36,4 @@ o.cfgvalue = function(t, n)
|
||||
return string.format('<font color="red">%s</font>', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
|
||||
end
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -8,6 +8,8 @@ local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
|
||||
local has_chnlist = fs.access("/usr/share/passwall/rules/chnlist")
|
||||
local has_chnroute = fs.access("/usr/share/passwall/rules/chnroute")
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@@ -879,4 +881,4 @@ footer.shunt_list = api.jsonc.stringify(shunt_list)
|
||||
m:append(footer)
|
||||
m:append(Template(appname .. "/global/status_bottom"))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -15,6 +15,8 @@ for k, e in ipairs(api.get_valid_nodes()) do
|
||||
end
|
||||
end
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname, "HAPROXY " .. translate("Load Balancing"))
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@@ -101,7 +103,8 @@ end
|
||||
o:depends("health_check_type", "passwall_logic")
|
||||
|
||||
-- [[ Balancing Settings ]]--
|
||||
s = m:section(TypedSection, "haproxy_config", translate("Node List"))
|
||||
local cfgname = "haproxy_config"
|
||||
s = m:section(TypedSection, cfgname, translate("Node List"))
|
||||
s.description = "<font color='red'>" ..
|
||||
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") .. "<br>" ..
|
||||
translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") .. "</font>"
|
||||
@@ -177,6 +180,11 @@ o:value(0, translate("Primary"))
|
||||
o:value(1, translate("Standby"))
|
||||
o.rmempty = false
|
||||
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
m:append(Template(appname .. "/haproxy/js"))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -1,6 +1,8 @@
|
||||
api = require "luci.passwall.api"
|
||||
appname = "passwall"
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname, translate("Node Config"))
|
||||
m.redirect = api.url("node_list")
|
||||
api.set_apply_on_parse(m)
|
||||
@@ -120,7 +122,6 @@ local footer = Template(appname .. "/node_config/footer")
|
||||
footer.api = api
|
||||
footer.config = m.config
|
||||
footer.section = arg[1]
|
||||
|
||||
m:append(footer)
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -3,6 +3,8 @@ local appname = "passwall"
|
||||
local sys = api.sys
|
||||
local datatypes = api.datatypes
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@@ -33,4 +35,4 @@ s:append(Template(appname .. "/node_list/link_add_node"))
|
||||
|
||||
m:append(Template(appname .. "/node_list/node_list"))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -38,6 +38,8 @@ if has_hysteria2 then
|
||||
table.insert(hysteria2_type, s)
|
||||
end
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@@ -127,7 +129,8 @@ o.cfgvalue = function(self, section)
|
||||
translate("Manual subscription All"))
|
||||
end
|
||||
|
||||
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
|
||||
local cfgname = "subscribe_list"
|
||||
s = m:section(TypedSection, cfgname, "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
s.sortable = true
|
||||
@@ -148,7 +151,7 @@ o.validate = function(self, value, section)
|
||||
return nil, translate("Remark cannot be empty.")
|
||||
end
|
||||
local duplicate = false
|
||||
m.uci:foreach(appname, "subscribe_list", function(e)
|
||||
m.uci:foreach(appname, cfgname, function(e)
|
||||
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
|
||||
duplicate = true
|
||||
return false
|
||||
@@ -223,6 +226,11 @@ o.cfgvalue = function(self, section)
|
||||
section, translate("Manual subscription"))
|
||||
end
|
||||
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
m:append(Template(appname .. "/node_subscribe/js"))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -2,6 +2,8 @@ local api = require "luci.passwall.api"
|
||||
local uci = api.uci
|
||||
local appname = "passwall"
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
m.redirect = api.url("node_subscribe")
|
||||
api.set_apply_on_parse(m)
|
||||
@@ -244,13 +246,7 @@ o:value(0, translate("Every Sunday"))
|
||||
o = s:option(Value, "update_time_mode", translate("Update Time"))
|
||||
for t = 0, 23 do o:value(t .. ":00") end
|
||||
o.default = "0:00"
|
||||
o.validate = function(self, value)
|
||||
local b = api.is_timehhmm(value)
|
||||
if b then
|
||||
return value
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o.datatype = "timehhmm"
|
||||
o:depends("update_week_mode", "0")
|
||||
o:depends("update_week_mode", "1")
|
||||
o:depends("update_week_mode", "2")
|
||||
@@ -321,4 +317,4 @@ for k, v in pairs(nodes_table) do
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -8,6 +8,8 @@ local port_validate = function(self, value, t)
|
||||
return value:gsub("-", ":")
|
||||
end
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@@ -43,13 +45,7 @@ for index, value in ipairs({"stop", "start", "restart"}) do
|
||||
o = s:option(Value, value .. "_time_mode", translate(value .. " Time"))
|
||||
for t = 0, 23 do o:value(t .. ":00") end
|
||||
o.default = "0:00"
|
||||
o.validate = function(self, value)
|
||||
local b = api.is_timehhmm(value)
|
||||
if b then
|
||||
return value
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o.datatype = "timehhmm"
|
||||
o:depends(value .. "_week_mode", "0")
|
||||
o:depends(value .. "_week_mode", "1")
|
||||
o:depends(value .. "_week_mode", "2")
|
||||
@@ -271,4 +267,4 @@ if has_singbox then
|
||||
o.default = 0
|
||||
end
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -3,6 +3,8 @@ local appname = "passwall"
|
||||
local has_xray = api.finded_com("xray")
|
||||
local has_singbox = api.finded_com("sing-box")
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@@ -112,13 +114,7 @@ o:value(0, translate("Every Sunday"))
|
||||
o = s:option(Value, "update_time_mode", translate("Update Time"))
|
||||
for t = 0, 23 do o:value(t .. ":00") end
|
||||
o.default = "0:00"
|
||||
o.validate = function(self, value)
|
||||
local b = api.is_timehhmm(value)
|
||||
if b then
|
||||
return value
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o.datatype = "timehhmm"
|
||||
o:depends("update_week_mode", "0")
|
||||
o:depends("update_week_mode", "1")
|
||||
o:depends("update_week_mode", "2")
|
||||
@@ -146,8 +142,10 @@ end
|
||||
|
||||
s:append(Template(appname .. "/rule/rule_version"))
|
||||
|
||||
local cfgname = "shunt_rules"
|
||||
|
||||
if has_xray or has_singbox then
|
||||
s = m:section(TypedSection, "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
|
||||
s = m:section(TypedSection, cfgname, "Sing-Box/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
|
||||
s.template = "cbi/tblsection"
|
||||
s.anonymous = false
|
||||
s.addremove = true
|
||||
@@ -169,4 +167,9 @@ if has_xray or has_singbox then
|
||||
o = s:option(DummyValue, "remarks", translate("Remarks"))
|
||||
end
|
||||
|
||||
return m
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
return api.return_map(m)
|
||||
@@ -1,5 +1,5 @@
|
||||
local api = require "luci.passwall.api"
|
||||
local appname = "passwall"
|
||||
local appname = api.appname
|
||||
local fs = api.fs
|
||||
local sys = api.sys
|
||||
local uci = api.uci
|
||||
|
||||
@@ -2,6 +2,8 @@ local api = require "luci.passwall.api"
|
||||
local appname = "passwall"
|
||||
local datatypes = api.datatypes
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
|
||||
m.redirect = api.url("rule")
|
||||
api.set_apply_on_parse(m)
|
||||
@@ -245,4 +247,4 @@ ip_list.description = "<br /><ul>"
|
||||
|
||||
o = s:option(Flag, "invert", "Invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box."))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -1,6 +1,8 @@
|
||||
local api = require "luci.passwall.api"
|
||||
local appname = "passwall"
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
m.redirect = api.url()
|
||||
api.set_apply_on_parse(m)
|
||||
@@ -166,4 +168,4 @@ o = s:option(DummyValue, "btn")
|
||||
o.template = appname .. "/socks_auto_switch/btn"
|
||||
o:depends("backup_node_add_mode", "manual")
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -667,6 +667,14 @@ o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in J
|
||||
o:depends({ [_n("use_xhttp_extra")] = true })
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "xhttp_extra")
|
||||
if raw then
|
||||
@@ -689,14 +697,6 @@ o.custom_write = function(self, section, value)
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
end
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
if api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
|
||||
end
|
||||
end
|
||||
o.custom_remove = function(self, section, value)
|
||||
m:del(section, "xhttp_extra")
|
||||
m:del(section, "download_address")
|
||||
@@ -752,6 +752,14 @@ o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
|
||||
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "finalmask")
|
||||
if raw then
|
||||
@@ -761,14 +769,6 @@ end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||
end
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
if api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||||
end
|
||||
end
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
local api = require "luci.passwall.api"
|
||||
local appname = api.appname
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map("passwall_server", translate("Server-Side"))
|
||||
api.set_apply_on_parse(m)
|
||||
@@ -10,7 +13,8 @@ t.addremove = false
|
||||
e = t:option(Flag, "enable", translate("Enable"))
|
||||
e.rmempty = false
|
||||
|
||||
t = m:section(TypedSection, "user", translate("Users Manager"))
|
||||
local cfgname = "user"
|
||||
t = m:section(TypedSection, cfgname, translate("Users Manager"))
|
||||
t.anonymous = true
|
||||
t.addremove = true
|
||||
t.sortable = true
|
||||
@@ -85,7 +89,14 @@ e = t:option(Flag, "log", translate("Log"))
|
||||
e.default = "1"
|
||||
e.rmempty = false
|
||||
|
||||
m:append(Template("passwall/server/log"))
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.appname = m.config
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
m:append(Template("passwall/server/users_list_status"))
|
||||
return m
|
||||
m:append(Template(appname .. "/server/log"))
|
||||
|
||||
m:append(Template(appname .. "/server/users_list_status"))
|
||||
|
||||
return api.return_map(m)
|
||||
@@ -131,12 +131,12 @@ o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
|
||||
@@ -406,6 +406,7 @@ o = s:option(TextValue, _n("finalmask"), "FinalMask JSON")
|
||||
o:depends({ [_n("use_finalmask")] = true })
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.datatype = "json"
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "finalmask")
|
||||
if raw then
|
||||
@@ -415,14 +416,6 @@ end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||
end
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
if api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||||
end
|
||||
end
|
||||
|
||||
--[[acceptProxyProtocol]]
|
||||
o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
|
||||
@@ -517,16 +510,16 @@ for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
|
||||
@@ -489,16 +489,16 @@ for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
|
||||
@@ -49,16 +49,16 @@ o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
|
||||
@@ -76,16 +76,16 @@ o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
|
||||
@@ -2,6 +2,8 @@ api = require "luci.passwall.api"
|
||||
appname = api.appname
|
||||
fs = api.fs
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map("passwall_server", translate("Server Config"))
|
||||
m.redirect = api.url("server")
|
||||
api.set_apply_on_parse(m)
|
||||
@@ -56,4 +58,4 @@ footer.config = m.config
|
||||
footer.section = arg[1]
|
||||
m:append(footer)
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
@@ -348,8 +348,16 @@ function strToTable(str)
|
||||
return loadstring("return " .. str)()
|
||||
end
|
||||
|
||||
function is_timehhmm(timeStr)
|
||||
local hour, minute = string.match(timeStr, "^(%d?%d):(%d%d)$")
|
||||
function is_json(str)
|
||||
if str and jsonc.parse(str) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
datatypes.json = is_json
|
||||
|
||||
function is_timehhmm(str)
|
||||
local hour, minute = string.match(str, "^(%d?%d):(%d%d)$")
|
||||
if hour and minute then
|
||||
hour = tonumber(hour)
|
||||
minute = tonumber(minute)
|
||||
@@ -359,6 +367,7 @@ function is_timehhmm(timeStr)
|
||||
end
|
||||
return false
|
||||
end
|
||||
datatypes.timehhmm = is_timehhmm
|
||||
|
||||
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
|
||||
@@ -1327,6 +1336,40 @@ function to_check_self()
|
||||
}
|
||||
end
|
||||
|
||||
function set_default_cbi()
|
||||
local cbi = require "luci.cbi"
|
||||
if true then
|
||||
--TextValue
|
||||
local TextValue = cbi.TextValue
|
||||
local original_init = TextValue.__init__
|
||||
function TextValue.__init__(self, ...)
|
||||
original_init(self, ...)
|
||||
self.template = appname .. "/cbi/tvalue"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function return_map(map)
|
||||
local cbi = require "luci.cbi"
|
||||
local api = require "luci.passwall.api"
|
||||
if true then
|
||||
-- header
|
||||
local header = cbi.Template(appname .. "/cbi/header")
|
||||
header.api = api
|
||||
header.config = map.config
|
||||
table.insert(map.children, 1, header)
|
||||
end
|
||||
if true then
|
||||
-- footer
|
||||
local footer = cbi.Template(appname .. "/cbi/footer")
|
||||
footer.api = api
|
||||
footer.config = map.config
|
||||
map:append(footer)
|
||||
end
|
||||
|
||||
return map
|
||||
end
|
||||
|
||||
function luci_types(id, m, s, type_name, option_prefix)
|
||||
local fv_type
|
||||
local field_type = s.fields["type"]
|
||||
@@ -1673,18 +1716,29 @@ function cleanEmptyTables(t)
|
||||
return next(t) and t or nil
|
||||
end
|
||||
|
||||
function fetch_cert_sha256(host, port, sni, timeout)
|
||||
function fetch_cert_sha256(host, port, sni, timeout, http3)
|
||||
if not host then return "" end
|
||||
port = tonumber(port) or 443
|
||||
sni = sni or host
|
||||
timeout = tonumber(timeout) or 5
|
||||
local cmd = string.format(
|
||||
"timeout %d openssl s_client -connect %s:%d -servername %s -showcerts </dev/null 2>/dev/null " ..
|
||||
"| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " ..
|
||||
"| openssl x509 -outform der 2>/dev/null " ..
|
||||
"| sha256sum 2>/dev/null",
|
||||
timeout, host, port, sni
|
||||
)
|
||||
local cmd
|
||||
if http3 then
|
||||
cmd = string.format(
|
||||
"timeout %d curl --http3 -k -w '%%{certs}' -o /dev/null https://%s:%d 2>/dev/null " ..
|
||||
"| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " ..
|
||||
"| openssl x509 -outform der 2>/dev/null " ..
|
||||
"| sha256sum 2>/dev/null",
|
||||
timeout, host, port
|
||||
)
|
||||
else
|
||||
cmd = string.format(
|
||||
"timeout %d openssl s_client -connect %s:%d -servername %s -showcerts </dev/null 2>/dev/null " ..
|
||||
"| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " ..
|
||||
"| openssl x509 -outform der 2>/dev/null " ..
|
||||
"| sha256sum 2>/dev/null",
|
||||
timeout, host, port, sni
|
||||
)
|
||||
end
|
||||
local out = trim(sys.exec(cmd))
|
||||
local fp = out:match("^([0-9a-fA-F]+)")
|
||||
if not fp or fp:lower():match("^e3b0c44298fc1c149afbf4c8996fb924") then
|
||||
|
||||
@@ -153,8 +153,8 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
tlsSettings = (node.stream_security == "tls") and {
|
||||
serverName = node.tls_serverName,
|
||||
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
|
||||
pinnedPeerCertSha256 = node.tls_pinSHA256,
|
||||
verifyPeerCertByName = node.tls_CertByName,
|
||||
pinnedPeerCertSha256 = node.tls_pinSHA256 or "",
|
||||
verifyPeerCertByName = node.tls_CertByName or "",
|
||||
echConfigList = (node.ech == "1") and node.ech_config or nil,
|
||||
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
|
||||
certificate = api.split(node.tls_certificate_pem, "\n"),
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!-- map footer -->
|
||||
@@ -0,0 +1,2 @@
|
||||
<!-- map header -->
|
||||
<script src="<%=resource%>/view/<%=self.api.appname%>/cbi.js"></script>
|
||||
@@ -403,7 +403,7 @@
|
||||
function lv_expandGroupOfCurrent(cbid, listContainer, hiddenSelect) {
|
||||
const key = hiddenSelect.options[0].value;
|
||||
if (!key) return;
|
||||
const targetLi = listContainer.querySelector("li[data-key='" + key.replace(/'/g,"\\'") + "']");
|
||||
const targetLi = listContainer.querySelector(`li[data-key='${CSS.escape(key)}']`);
|
||||
if (!targetLi) return;
|
||||
let parentGroup = targetLi.closest(".lv-group");
|
||||
if (parentGroup) {
|
||||
|
||||
@@ -402,7 +402,7 @@
|
||||
function v_expandGroupOfCurrent(cbid, listContainer, hiddenInput) {
|
||||
const key = hiddenInput.value;
|
||||
if (!key) return;
|
||||
const targetLi = listContainer.querySelector("li[data-key='" + key.replace(/'/g,"\\'") + "']");
|
||||
const targetLi = listContainer.querySelector(`li[data-key='${CSS.escape(key)}']`);
|
||||
if (!targetLi) return;
|
||||
let parentGroup = targetLi.closest(".v-group");
|
||||
if (parentGroup) {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<%
|
||||
local api = self.api
|
||||
local appname = self.appname or api.appname
|
||||
local target_cfgname = self.target_cfgname
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
||||
<style>
|
||||
table .cbi-button-up,
|
||||
table .cbi-button-down,
|
||||
.td.cbi-section-actions .cbi-button-up,
|
||||
.td.cbi-section-actions .cbi-button-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
vertical-align: middle;
|
||||
cursor: grab !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
padding: 0 !important;
|
||||
line-height: inherit;
|
||||
user-select: none;
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dragging-row {
|
||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
#cbi-<%=appname%>-<%=target_cfgname%> .cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
//<![CDATA[
|
||||
//Add drag-and-drop sorting
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSortableForTable() {
|
||||
var section = document.getElementById("cbi-<%=appname%>-<%=target_cfgname%>");
|
||||
if (!section) return;
|
||||
|
||||
// === insert drag handle ===
|
||||
var rows = section.querySelectorAll("tr");
|
||||
rows.forEach(function(row) {
|
||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||
if (!btn) return;
|
||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||
var handle = document.createElement("span");
|
||||
handle.className = "drag-handle center";
|
||||
handle.title = "<%:Drag to reorder%>";
|
||||
handle.innerHTML = "⠿";
|
||||
btn.after(handle);
|
||||
});
|
||||
|
||||
// === init Sortable ===
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if (!table) return;
|
||||
var root = table.tBodies[0] || table;
|
||||
if (root._sortable_initialized) return root._sortable_instance;
|
||||
root._sortable_initialized = true;
|
||||
|
||||
// save origin order
|
||||
root._origOrder = getCurrentOrder(root);
|
||||
|
||||
try {
|
||||
root._sortable_instance = Sortable.create(root, {
|
||||
handle: ".drag-handle",
|
||||
draggable: "tr.cbi-section-table-row",
|
||||
animation: 150,
|
||||
ghostClass: "dragging-row",
|
||||
fallbackOnBody: true,
|
||||
forceFallback: false,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function (evt) {
|
||||
updateHiddenInput(root, section);
|
||||
}
|
||||
});
|
||||
return root._sortable_instance;
|
||||
} catch (e) {
|
||||
root._sortable_initialized = false;
|
||||
console.error("Sortable init failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// get table current line order
|
||||
function getCurrentOrder(root) {
|
||||
var order = [];
|
||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||
rows.forEach(function (tr) {
|
||||
var id = tr.id || "";
|
||||
if (id.startsWith("cbi-<%=appname%>-"))
|
||||
id = id.replace("cbi-<%=appname%>-", "");
|
||||
order.push(id);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// update after drag and drop hidden input
|
||||
function updateHiddenInput(root, section) {
|
||||
var newOrder = getCurrentOrder(root);
|
||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||
}
|
||||
}
|
||||
|
||||
// hide lua luci version up/down
|
||||
function hideSortColumn(section) {
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if(!table) return;
|
||||
var rows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||
if(!rows.length) return;
|
||||
var colCount = rows[0].children.length, sortCol = -1;
|
||||
for(var col=0; col<colCount; col++){
|
||||
var hasSort=false, invalid=false;
|
||||
rows.forEach(function(tr){
|
||||
var td=tr.children[col]; if(!td) return;
|
||||
if(td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid=true;
|
||||
if(td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort=true;
|
||||
});
|
||||
if(!invalid && hasSort){ sortCol=col; break; }
|
||||
}
|
||||
if(sortCol===-1) return;
|
||||
table.querySelectorAll("tr").forEach(function(tr){
|
||||
var c=tr.children[sortCol]; if(c) c.style.display="none";
|
||||
});
|
||||
}
|
||||
|
||||
// === wait TypedSection ===
|
||||
(function waitStable() {
|
||||
var last = 0, stable = 0;
|
||||
var THRESHOLD = 5;
|
||||
function tick() {
|
||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||
if (count && count === last) stable++;
|
||||
else stable = 0;
|
||||
|
||||
last = count;
|
||||
if (stable >= THRESHOLD)
|
||||
setTimeout(initSortableForTable, 200);
|
||||
else
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<%+cbi/valueheader%>
|
||||
<textarea class="cbi-input-textarea" <% if not self.size then %> style="width: 100%"<% else %> cols="<%=self.size%>"<% end %>
|
||||
data-update="change"<%=
|
||||
attr("name", cbid) ..
|
||||
attr("id", cbid) ..
|
||||
ifattr(self.rows, "rows") ..
|
||||
ifattr(self.wrap, "wrap") ..
|
||||
ifattr(self.readonly, "readonly") ..
|
||||
ifattr(self.datatype, "data-type", self.datatype)
|
||||
%>>
|
||||
<%-=pcdata(self:cfgvalue(section) or self.default)-%>
|
||||
</textarea>
|
||||
<%+cbi/valuefooter%>
|
||||
@@ -2,46 +2,8 @@
|
||||
local api = require "luci.passwall.api"
|
||||
local appname = api.appname
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js?v=1.15.7"></script>
|
||||
|
||||
<style>
|
||||
table .cbi-button-up,
|
||||
table .cbi-button-down,
|
||||
.td.cbi-section-actions .cbi-button-up,
|
||||
.td.cbi-section-actions .cbi-button-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
vertical-align: middle;
|
||||
cursor: grab !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
padding: 0 !important;
|
||||
line-height: inherit;
|
||||
user-select: none;
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dragging-row {
|
||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
#cbi-<%=appname%>-haproxy_config .cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1152px) {
|
||||
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".haproxy_port"],
|
||||
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".lbweight"] {
|
||||
@@ -110,129 +72,5 @@ table .cbi-button-down,
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
//节点列表添加拖拽排序
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSortableForTable() {
|
||||
var section = document.getElementById("cbi-<%=appname%>-haproxy_config");
|
||||
if (!section) return;
|
||||
|
||||
hideSortColumn(section);
|
||||
|
||||
// === 插入 drag handle ===
|
||||
var rows = section.querySelectorAll("tr");
|
||||
rows.forEach(function(row) {
|
||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||
if (!btn) return;
|
||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||
var handle = document.createElement("span");
|
||||
handle.className = "drag-handle center";
|
||||
handle.title = "<%:Drag to reorder%>";
|
||||
handle.innerHTML = "⠿";
|
||||
btn.after(handle);
|
||||
});
|
||||
|
||||
// === 初始化 Sortable ===
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if (!table) return;
|
||||
var root = table.tBodies[0] || table;
|
||||
if (root._sortable_initialized) return root._sortable_instance;
|
||||
root._sortable_initialized = true;
|
||||
|
||||
// 保存原始顺序
|
||||
root._origOrder = getCurrentOrder(root);
|
||||
|
||||
try {
|
||||
root._sortable_instance = Sortable.create(root, {
|
||||
handle: ".drag-handle",
|
||||
draggable: "tr.cbi-section-table-row",
|
||||
animation: 150,
|
||||
ghostClass: "dragging-row",
|
||||
fallbackOnBody: true,
|
||||
forceFallback: false,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function (evt) {
|
||||
updateHiddenInput(root, section);
|
||||
}
|
||||
});
|
||||
return root._sortable_instance;
|
||||
} catch (e) {
|
||||
root._sortable_initialized = false;
|
||||
console.error("Sortable init failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 table 当前行顺序
|
||||
function getCurrentOrder(root) {
|
||||
var order = [];
|
||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||
rows.forEach(function (tr) {
|
||||
var id = tr.id || "";
|
||||
if (id.startsWith("cbi-<%=appname%>-"))
|
||||
id = id.replace("cbi-<%=appname%>-", "");
|
||||
order.push(id);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// 拖拽完成后更新 hidden input
|
||||
function updateHiddenInput(root, section) {
|
||||
var newOrder = getCurrentOrder(root);
|
||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏18.06 up/down 列
|
||||
function hideSortColumn(section) {
|
||||
var table = section.querySelector("table");
|
||||
if (!table) return;
|
||||
var ths = Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-titles > th"));
|
||||
var dataRows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||
if (!ths.length || !dataRows.length) return;
|
||||
var sortCol = -1;
|
||||
for (var i = 0; i < ths.length; i++) {
|
||||
var hasSort = false, invalid = false;
|
||||
dataRows.forEach(function(tr) {
|
||||
var td = tr.querySelectorAll(":scope > td")[i];
|
||||
if (!td) return;
|
||||
if (td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid = true;
|
||||
if (td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort = true;
|
||||
});
|
||||
if (!invalid && hasSort) { sortCol = i; break; }
|
||||
}
|
||||
if (sortCol === -1) return;
|
||||
var rows = [table.querySelector("tr.cbi-section-table-titles")].concat(
|
||||
Array.prototype.slice.call(dataRows),
|
||||
Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-descr"))
|
||||
);
|
||||
rows.forEach(function(tr) {
|
||||
var cells = Array.prototype.filter.call(tr.children, function(el) {
|
||||
return el.tagName === "TH" || el.tagName === "TD";
|
||||
});
|
||||
if (cells[sortCol]) cells[sortCol].style.display = "none";
|
||||
});
|
||||
}
|
||||
|
||||
// === 等待 TypedSection 行稳定 ===
|
||||
(function waitStable() {
|
||||
var last = 0, stable = 0;
|
||||
var THRESHOLD = 5;
|
||||
function tick() {
|
||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||
if (count && count === last) stable++;
|
||||
else stable = 0;
|
||||
|
||||
last = count;
|
||||
if (stable >= THRESHOLD)
|
||||
setTimeout(initSortableForTable, 200);
|
||||
else
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
})();
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
@@ -67,7 +67,10 @@ local api = self.api
|
||||
return;
|
||||
}
|
||||
if (!res || res.code !== 1 || !res.data) {
|
||||
alert(res && res.msg ? res.msg : "<%:Fetch Failed%> !");
|
||||
alert(res && res.msg ? res.msg : "<%:Fetch Failed!%>\n" +
|
||||
"<%:Requires OpenSSL to retrieve the TLS certificate chain fingerprint.%>\n" +
|
||||
"<%:QUIC protocols such as HY2 also require curl with http3 support.%>"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const inputs = document.querySelectorAll('input[id$=".xray_tls_pinSHA256"]');
|
||||
|
||||
@@ -635,11 +635,15 @@ table td, .table .td {
|
||||
if (cacheData && cacheData.savetime && (now - cacheData.timestamp) < cacheData.savetime) {
|
||||
const a = ping_value[i].firstElementChild;
|
||||
let color = "red";
|
||||
if (cacheData.value < 100)
|
||||
color = "green";
|
||||
else if (cacheData.value < 200)
|
||||
color = "#fb9a05";
|
||||
a.innerText = cacheData.value + " ms";
|
||||
if (cacheData.value >= 0) {
|
||||
a.innerText = cacheData.value + " ms";
|
||||
if (cacheData.value < 100)
|
||||
color = "green";
|
||||
else if (cacheData.value < 200)
|
||||
color = "#fb9a05";
|
||||
} else {
|
||||
a.innerText = "<%:Timeout%>";
|
||||
}
|
||||
a.style.color = color;
|
||||
a.onclick = function () {
|
||||
ping_node(cbi_id, this, auto_detection_time);
|
||||
@@ -679,15 +683,23 @@ table td, .table .td {
|
||||
const cache_data = {
|
||||
dom_id: strs[i],
|
||||
timestamp: Date.now(),
|
||||
savetime: 60 * 1000,
|
||||
savetime: 150 * 1000,
|
||||
value: ping
|
||||
};
|
||||
text = result.ping + " ms";
|
||||
localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data));
|
||||
text = result.ping + " ms";
|
||||
if (ping < 100)
|
||||
color = "green";
|
||||
else if (ping < 200)
|
||||
color = "#fb9a05";
|
||||
} else {
|
||||
const cache_data = {
|
||||
dom_id: strs[i],
|
||||
timestamp: Date.now(),
|
||||
savetime: 60 * 1000,
|
||||
value: -1
|
||||
};
|
||||
localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data));
|
||||
}
|
||||
a.innerText = text;
|
||||
a.style.color = color;
|
||||
@@ -1101,6 +1113,15 @@ table td, .table .td {
|
||||
onChange("auto_detection_time", auto_detection_time);
|
||||
});
|
||||
});
|
||||
|
||||
const links = document.querySelectorAll('a');
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
if (link.getAttribute('target') !== '_blank') {
|
||||
ajaxAbortAll();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
@@ -1,46 +1,6 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js?v=1.15.7"></script>
|
||||
|
||||
<style>
|
||||
table .cbi-button-up,
|
||||
table .cbi-button-down,
|
||||
.td.cbi-section-actions .cbi-button-up,
|
||||
.td.cbi-section-actions .cbi-button-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
vertical-align: middle;
|
||||
cursor: grab !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
padding: 0 !important;
|
||||
line-height: inherit;
|
||||
user-select: none;
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dragging-row {
|
||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
#cbi-<%=api.appname%>-subscribe_list .cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
@@ -148,129 +108,5 @@ table .cbi-button-down,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//订阅列表添加拖拽排序
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSortableForTable() {
|
||||
var section = document.getElementById("cbi-<%=api.appname%>-subscribe_list");
|
||||
if (!section) return;
|
||||
|
||||
hideSortColumn(section);
|
||||
|
||||
// === 插入 drag handle ===
|
||||
var rows = section.querySelectorAll("tr");
|
||||
rows.forEach(function(row) {
|
||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||
if (!btn) return;
|
||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||
var handle = document.createElement("span");
|
||||
handle.className = "drag-handle center";
|
||||
handle.title = "<%:Drag to reorder%>";
|
||||
handle.innerHTML = "⠿";
|
||||
btn.after(handle);
|
||||
});
|
||||
|
||||
// === 初始化 Sortable ===
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if (!table) return;
|
||||
var root = table.tBodies[0] || table;
|
||||
if (root._sortable_initialized) return root._sortable_instance;
|
||||
root._sortable_initialized = true;
|
||||
|
||||
// 保存原始顺序
|
||||
root._origOrder = getCurrentOrder(root);
|
||||
|
||||
try {
|
||||
root._sortable_instance = Sortable.create(root, {
|
||||
handle: ".drag-handle",
|
||||
draggable: "tr.cbi-section-table-row",
|
||||
animation: 150,
|
||||
ghostClass: "dragging-row",
|
||||
fallbackOnBody: true,
|
||||
forceFallback: false,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function (evt) {
|
||||
updateHiddenInput(root, section);
|
||||
}
|
||||
});
|
||||
return root._sortable_instance;
|
||||
} catch (e) {
|
||||
root._sortable_initialized = false;
|
||||
console.error("Sortable init failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 table 当前行顺序
|
||||
function getCurrentOrder(root) {
|
||||
var order = [];
|
||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||
rows.forEach(function (tr) {
|
||||
var id = tr.id || "";
|
||||
if (id.startsWith("cbi-<%=api.appname%>-"))
|
||||
id = id.replace("cbi-<%=api.appname%>-", "");
|
||||
order.push(id);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// 拖拽完成后更新 hidden input
|
||||
function updateHiddenInput(root, section) {
|
||||
var newOrder = getCurrentOrder(root);
|
||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏18.06 up/down 列
|
||||
function hideSortColumn(section) {
|
||||
var table = section.querySelector("table");
|
||||
if (!table) return;
|
||||
var ths = Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-titles > th"));
|
||||
var dataRows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||
if (!ths.length || !dataRows.length) return;
|
||||
var sortCol = -1;
|
||||
for (var i = 0; i < ths.length; i++) {
|
||||
var hasSort = false, invalid = false;
|
||||
dataRows.forEach(function(tr) {
|
||||
var td = tr.querySelectorAll(":scope > td")[i];
|
||||
if (!td) return;
|
||||
if (td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid = true;
|
||||
if (td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort = true;
|
||||
});
|
||||
if (!invalid && hasSort) { sortCol = i; break; }
|
||||
}
|
||||
if (sortCol === -1) return;
|
||||
var rows = [table.querySelector("tr.cbi-section-table-titles")].concat(
|
||||
Array.prototype.slice.call(dataRows),
|
||||
Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-descr"))
|
||||
);
|
||||
rows.forEach(function(tr) {
|
||||
var cells = Array.prototype.filter.call(tr.children, function(el) {
|
||||
return el.tagName === "TH" || el.tagName === "TD";
|
||||
});
|
||||
if (cells[sortCol]) cells[sortCol].style.display = "none";
|
||||
});
|
||||
}
|
||||
|
||||
// === 等待 TypedSection 行稳定 ===
|
||||
(function waitStable() {
|
||||
var last = 0, stable = 0;
|
||||
var THRESHOLD = 5;
|
||||
function tick() {
|
||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||
if (count && count === last) stable++;
|
||||
else stable = 0;
|
||||
|
||||
last = count;
|
||||
if (stable >= THRESHOLD)
|
||||
setTimeout(initSortableForTable, 200);
|
||||
else
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
})();
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
@@ -4,16 +4,8 @@ local fs = api.fs
|
||||
local has_old_geoip = fs.access("/tmp/bak_v2ray/geoip.dat")
|
||||
local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js?v=1.15.7"></script>
|
||||
|
||||
<style>
|
||||
table .cbi-button-up,
|
||||
table .cbi-button-down,
|
||||
.td.cbi-section-actions .cbi-button-up,
|
||||
.td.cbi-section-actions .cbi-button-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.cbi-value[id$="-gfwlist_update"],
|
||||
div.cbi-value[id$="-chnroute_update"],
|
||||
div.cbi-value[id$="-chnroute6_update"],
|
||||
@@ -22,30 +14,6 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
||||
div.cbi-value[id$="-geosite_update"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
vertical-align: middle;
|
||||
cursor: grab !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
padding: 0 !important;
|
||||
line-height: inherit;
|
||||
user-select: none;
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dragging-row {
|
||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="cbi-value" id="_rule_div">
|
||||
@@ -219,120 +187,5 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//分流规则添加拖拽排序
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSortableForTable() {
|
||||
var section = document.getElementById("cbi-<%=api.appname%>-shunt_rules");
|
||||
if (!section) return;
|
||||
|
||||
hideSortColumn(section);
|
||||
|
||||
// === 插入 drag handle ===
|
||||
var rows = section.querySelectorAll("tr");
|
||||
rows.forEach(function(row) {
|
||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||
if (!btn) return;
|
||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||
var handle = document.createElement("span");
|
||||
handle.className = "drag-handle center";
|
||||
handle.title = "<%:Drag to reorder%>";
|
||||
handle.innerHTML = "⠿";
|
||||
btn.after(handle);
|
||||
});
|
||||
|
||||
// === 初始化 Sortable ===
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if (!table) return;
|
||||
var root = table.tBodies[0] || table;
|
||||
if (root._sortable_initialized) return root._sortable_instance;
|
||||
root._sortable_initialized = true;
|
||||
|
||||
// 保存原始顺序
|
||||
root._origOrder = getCurrentOrder(root);
|
||||
|
||||
try {
|
||||
root._sortable_instance = Sortable.create(root, {
|
||||
handle: ".drag-handle",
|
||||
draggable: "tr.cbi-section-table-row",
|
||||
animation: 150,
|
||||
ghostClass: "dragging-row",
|
||||
fallbackOnBody: true,
|
||||
forceFallback: false,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function (evt) {
|
||||
updateHiddenInput(root, section);
|
||||
}
|
||||
});
|
||||
return root._sortable_instance;
|
||||
} catch (e) {
|
||||
root._sortable_initialized = false;
|
||||
console.error("Sortable init failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 table 当前行顺序
|
||||
function getCurrentOrder(root) {
|
||||
var order = [];
|
||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||
rows.forEach(function (tr) {
|
||||
var id = tr.id || "";
|
||||
if (id.startsWith("cbi-<%=api.appname%>-"))
|
||||
id = id.replace("cbi-<%=api.appname%>-", "");
|
||||
order.push(id);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// 拖拽完成后更新 hidden input
|
||||
function updateHiddenInput(root, section) {
|
||||
var newOrder = getCurrentOrder(root);
|
||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏18.06 up/down 列
|
||||
function hideSortColumn(section) {
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if(!table) return;
|
||||
var rows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||
if(!rows.length) return;
|
||||
var colCount = rows[0].children.length, sortCol = -1;
|
||||
for(var col=0; col<colCount; col++){
|
||||
var hasSort=false, invalid=false;
|
||||
rows.forEach(function(tr){
|
||||
var td=tr.children[col]; if(!td) return;
|
||||
if(td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid=true;
|
||||
if(td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort=true;
|
||||
});
|
||||
if(!invalid && hasSort){ sortCol=col; break; }
|
||||
}
|
||||
if(sortCol===-1) return;
|
||||
table.querySelectorAll("tr").forEach(function(tr){
|
||||
var c=tr.children[sortCol]; if(c) c.style.display="none";
|
||||
});
|
||||
}
|
||||
|
||||
// === 等待 TypedSection 行稳定 ===
|
||||
(function waitStable() {
|
||||
var last = 0, stable = 0;
|
||||
var THRESHOLD = 5;
|
||||
function tick() {
|
||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||
if (count && count === last) stable++;
|
||||
else stable = 0;
|
||||
|
||||
last = count;
|
||||
if (stable >= THRESHOLD)
|
||||
setTimeout(initSortableForTable, 200);
|
||||
else
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
})();
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
@@ -1862,8 +1862,14 @@ msgstr "TLS 用于验证 leaf 证书的 name。"
|
||||
msgid "Fetch Manually"
|
||||
msgstr "手动获取"
|
||||
|
||||
msgid "Fetch Failed"
|
||||
msgstr "获取失败"
|
||||
msgid "Fetch Failed!"
|
||||
msgstr "获取失败!"
|
||||
|
||||
msgid "Requires OpenSSL to retrieve the TLS certificate chain fingerprint."
|
||||
msgstr "获取 TLS 证书链指纹需要 OpenSSL 支持。"
|
||||
|
||||
msgid "QUIC protocols such as HY2 also require curl with http3 support."
|
||||
msgstr "HY2 等 QUIC 协议还需要 curl 支持 http3。"
|
||||
|
||||
msgid "TLS Certificate (PEM)"
|
||||
msgstr "TLS 证书(PEM)"
|
||||
@@ -2180,3 +2186,6 @@ msgstr "Gecko 包大小(最小)"
|
||||
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "Gecko 包大小(最大)"
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "有效时间(hh:mm)"
|
||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.7.16
|
||||
PKG_RELEASE:=63
|
||||
PKG_RELEASE:=64
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -177,8 +177,8 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
tlsSettings = (node.stream_security == "tls") and {
|
||||
serverName = node.tls_serverName,
|
||||
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
|
||||
pinnedPeerCertSha256 = node.tls_pinSHA256,
|
||||
verifyPeerCertByName = node.tls_CertByName,
|
||||
pinnedPeerCertSha256 = node.tls_pinSHA256 or "",
|
||||
verifyPeerCertByName = node.tls_CertByName or "",
|
||||
echConfigList = (node.ech == "1") and node.ech_config or nil,
|
||||
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
|
||||
certificate = api.split(node.tls_certificate_pem, "\n"),
|
||||
|
||||
@@ -403,7 +403,7 @@
|
||||
function lv_expandGroupOfCurrent(cbid, listContainer, hiddenSelect) {
|
||||
const key = hiddenSelect.options[0].value;
|
||||
if (!key) return;
|
||||
const targetLi = listContainer.querySelector("li[data-key='" + key.replace(/'/g,"\\'") + "']");
|
||||
const targetLi = listContainer.querySelector(`li[data-key='${CSS.escape(key)}']`);
|
||||
if (!targetLi) return;
|
||||
let parentGroup = targetLi.closest(".lv-group");
|
||||
if (parentGroup) {
|
||||
|
||||
@@ -402,7 +402,7 @@
|
||||
function v_expandGroupOfCurrent(cbid, listContainer, hiddenInput) {
|
||||
const key = hiddenInput.value;
|
||||
if (!key) return;
|
||||
const targetLi = listContainer.querySelector("li[data-key='" + key.replace(/'/g,"\\'") + "']");
|
||||
const targetLi = listContainer.querySelector(`li[data-key='${CSS.escape(key)}']`);
|
||||
if (!targetLi) return;
|
||||
let parentGroup = targetLi.closest(".v-group");
|
||||
if (parentGroup) {
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=virtualdog
|
||||
PKG_VERSION:=0.0.2
|
||||
PKG_RELEASE:=2
|
||||
PKG_VERSION:=0.0.3
|
||||
PKG_RELEASE:=3
|
||||
# Point this at the released binary tarball when you publish builds.
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/Carseason/openwrt-packages/releases/download/prebuilt/
|
||||
|
||||
+2
-2
@@ -5,13 +5,13 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
# The name and version of your package are used to define the variable to point to the build directory of your package: $(PKG_BUILD_DIR)
|
||||
PKG_NAME:=xt_tls
|
||||
PKG_VERSION:=0.3.5
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/Lochnair/xt_tls.git
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_SUBDIR=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_SOURCE_VERSION:=e393d3a343ad94b5b891b7dabc83fabf22742954
|
||||
PKG_SOURCE_VERSION:=137644fe539dd910c63ce527f6456ab38f148e3f
|
||||
|
||||
PKG_MAINTAINER:=Nils Andreas Svee <me@lochnair.net>
|
||||
PKG_LICENSE=GPL-3.0
|
||||
|
||||
Reference in new issue
Block a user