Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion applications/luci-app-passwall/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk

PKG_NAME:=luci-app-passwall
PKG_VERSION:=25.12.25
PKG_VERSION:=26.1.1
PKG_RELEASE:=1

PKG_CONFIG_DEPENDS:= \
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local api = require "luci.passwall.api"
local appname = "passwall"
local appname = api.appname
local datatypes = api.datatypes
local net = require "luci.model.network".init()

Expand All @@ -9,14 +9,17 @@ for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = {
id = e[".name"],
obj = e,
remarks = e["remark"]
remarks = e["remark"],
group = e["group"]
}
end
end

m = Map(appname)
api.set_apply_on_parse(m)

m:append(Template(appname .. "/cbi/nodes_value_com"))

-- [[ Haproxy Settings ]]--
s = m:section(TypedSection, "global_haproxy", translate("Basic Settings"))
s.anonymous = true
Expand Down Expand Up @@ -115,10 +118,15 @@ o.rmempty = false

---- Node Address
o = s:option(Value, "lbss", translate("Node Address"))
for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
o.template = appname .. "/cbi/nodes_value"
o.group = {}
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
o.rmempty = false
o.validate = function(self, value)
if not value then return nil end
if not value then return nil, translate("Node address cannot be empty.") end
local t = m:get(value) or nil
if t and t[".type"] == "nodes" then
return value
Expand All @@ -129,7 +137,7 @@ o.validate = function(self, value)
if api.is_ipv6addrport(value) then
return value
end
return nil, value
return nil, translate("Not valid IP format, please re-enter!") .. " (IP:Port)"
end

---- Haproxy Port
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -730,11 +730,21 @@ o:depends({ [_n("mux")] = true })

o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
o.default = 0
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "trojan" })

--[[tcpMptcp]]
o = s:option(Flag, _n("tcpMptcp"), "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
o.default = 0

o = s:option(Value, _n("preconns"), translate("Pre-connections"), translate("Number of early established connections to reduce latency."))
o.datatype = "uinteger"
o.placeholder = 0
o:depends({ [_n("protocol")] = "vless" })

o = s:option(ListValue, _n("chain_proxy"), translate("Chain Proxy"))
o:value("", translate("Close(Not use)"))
o:value("1", translate("Preproxy Node"))
Expand Down
6 changes: 3 additions & 3 deletions applications/luci-app-passwall/luasrc/passwall/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,9 @@ function url(...)
end

function trim(s)
local len = #s
local i, j = 1, len
while i <= len and s:byte(i) <= 32 do i = i + 1 end
if type(s) ~= "string" then return "" end
local i, j = 1, #s
while i <= j and s:byte(i) <= 32 do i = i + 1 end
while j >= i and s:byte(j) <= 32 do j = j - 1 end
if i > j then return "" end
return s:sub(i, j)
Expand Down
32 changes: 20 additions & 12 deletions applications/luci-app-passwall/luasrc/passwall/util_xray.lua
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ function gen_outbound(flag, node, tag, proxy_table)
id = node.uuid,
level = 0,
security = (node.protocol == "vmess") and node.security or nil,
testpre = (node.protocol == "vless") and tonumber(node.preconns) or nil,
encryption = (node.protocol == "vless") and ((node.encryption and node.encryption ~= "") and node.encryption or "none") or nil,
flow = (node.protocol == "vless"
and (node.tls == "1" or (node.encryption and node.encryption ~= "" and node.encryption ~= "none"))
Expand Down Expand Up @@ -627,6 +628,7 @@ function gen_config(var)
local dns = nil
local fakedns = nil
local routing = nil
local observatory = nil
local burstObservatory = nil
local strategy = nil
local inbounds = {}
Expand Down Expand Up @@ -867,18 +869,23 @@ function gen_config(var)
fallbackTag = fallback_node_tag,
strategy = strategy
})
if _node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" or fallback_node_tag then
if not burstObservatory then
burstObservatory = {
subjectSelector = { "blc-" },
pingConfig = {
destination = _node.useCustomProbeUrl and _node.probeUrl or nil,
interval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m",
sampling = 3,
timeout = "5s"
}
if _node.balancingStrategy == "leastPing" and not observatory then
observatory = {
subjectSelector = { "blc-" },
probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil,
probeInterval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m",
enableConcurrency = true
}
elseif _node.balancingStrategy == "leastLoad" and not burstObservatory then
burstObservatory = {
subjectSelector = { "blc-" },
pingConfig = {
destination = _node.useCustomProbeUrl and _node.probeUrl or nil,
interval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m",
sampling = 3,
timeout = "5s"
}
end
}
end
local inbound_tag = gen_loopback(loopback_tag, loopback_dst)
table.insert(rules, { inboundTag = { inbound_tag }, balancerTag = balancer_tag })
Expand Down Expand Up @@ -1397,7 +1404,7 @@ function gen_config(var)
address = remote_dns_udp_server or remote_dns_tcp_server,
port = tonumber(remote_dns_udp_port) or tonumber(remote_dns_tcp_port),
network = remote_dns_udp_server and "udp" or "tcp",
nonIPQuery = "drop"
nonIPQuery = "reject"
}
})

Expand Down Expand Up @@ -1514,6 +1521,7 @@ function gen_config(var)
-- 传出连接
outbounds = outbounds,
-- 连接观测
observatory = (not burstObservatory) and observatory or nil,
burstObservatory = burstObservatory,
-- 路由
routing = routing,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
<div id="<%=cbid%>.panel" class="cbi-listvalue-panel lv-dropdown-panel" style="display:none;">
<!-- 搜索框 -->
<div style="padding:8px;border-bottom:1px solid #f0f0f0;">
<input id="<%=cbid%>.search" class="cbi-input-text lv-dropdown-search" type="text" placeholder="<%:Search nodes...%>" />
<input id="<%=cbid%>.search" class="cbi-input-text lv-dropdown-search" type="text" placeholder="🔍 <%:Search nodes...%>" inputmode="search" enterkeyhint="done" />
</div>
<!-- 列表容器 -->
<div id="<%=cbid%>.list" style="padding:8px;">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
-- Copyright: copyright(c)2025–2027
-- Description: Passwall(2) UI template
-- It is the common part of the template and cannot be used independently

local api = require "luci.passwall.api"
local appname = api.appname

%>
<style>
/* 主下拉按钮的下箭头 */
Expand Down Expand Up @@ -73,6 +69,18 @@
min-width: 220px;
white-space: nowrap;
}
@media (max-width: 1152px) {
.lv-dropdown-container {
white-space: normal;
}
}
@media (max-width: 600px) {
.lv-dropdown-container {
display: block;
white-space: normal;
}
}

.lv-dropdown-display {
cursor: pointer;
display: inline-flex;
Expand Down Expand Up @@ -128,9 +136,6 @@
overflow: hidden;
text-overflow: ellipsis;
text-align: left !important;
}
#cbi-<%=appname%>-socks .td.cbi-value-field > div {
white-space: nowrap;
}
</style>

Expand Down Expand Up @@ -257,12 +262,16 @@

const styleNode = document.createElement('style')
const styleNames = ["width", "color", "height", "padding", "margin", "lineHeight", "border", "borderRadius", "minWidth", "minHeight"]
if (styleSelect.borderBottomStyle !== "none") {
styleNames.push("borderBottomWidth", "borderBottomStyle", "borderBottomColor");
}
document.head.appendChild(styleNode)
// trace back from option -> select -> body for background color
const panelRadius = styleSelect.borderRadius;
const optionColor = !lv_isTransparent(styleOption.backgroundColor) ? styleOption.backgroundColor : !lv_isTransparent(styleSelect.backgroundColor) ? styleSelect.backgroundColor : styleBody.backgroundColor
const titleColor = lv_getColorSchema(optionColor) === "light" ? lv_darker(optionColor, 30) : lv_lighter(optionColor, 30)
const selectStyleCSS = [`#${CSS.escape(cbid + ".display")} {`, lv_style2Css(styleSelect, styleNames), lv_style2Css(styleSelect, ["backgroundColor"]), "}"]
const optionStyleCSS = [`#${CSS.escape(cbid + ".panel")} {`, lv_style2Css(styleOption, styleNames), `background-color: ${optionColor};`, "}"]
const optionStyleCSS = [`#${CSS.escape(cbid + ".panel")} {`, lv_style2Css(styleOption, styleNames), `background-color: ${optionColor};`, `border-radius: ${panelRadius};`, "}"]
const titleStyleCSS = [`#${CSS.escape(cbid + ".panel")} .lv-group-title {`, `background-color: ${titleColor} !important;`, "}"]
styleNode.textContent = [].concat(selectStyleCSS, optionStyleCSS, titleStyleCSS).join("\n")
}
Expand Down Expand Up @@ -642,6 +651,13 @@
lv_filterList(this.value, cbid, listContainer, hiddenSelect, searchInput);
lv_repositionPanel(panel, display);
});
searchInput.addEventListener('keydown', function(e) {
const isEnter = e.key === "Enter" || e.keyCode === 13;
if (!isEnter) return;
e.stopPropagation();
e.preventDefault();
searchInput.blur();
});

// 切换组
listContainer.querySelectorAll(".lv-group-title").forEach(title => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<!-- 搜索框 -->
<input type="text" id="<%=cbid%>.search"
class="mv_search_input cbi-input-text"
placeholder="<%:Search nodes...%>" />
placeholder="🔍 <%:Search nodes...%>" inputmode="search" enterkeyhint="done" />
<!-- 主容器 -->
<div class="mv_list_container">
<ul class="cbi-multi mv_node_list" id="<%=cbid%>.node_list">
Expand All @@ -76,7 +76,7 @@
<!-- 组标题 -->
<div class="group-title">
<span id="arrow-<%=self.option%>-<%=idSafe(gname)%>" class="mv-arrow-right"></span>
<b style="margin-left:8px;"><%=gname%></b>
<b style="margin-left:8px;"><%=pcdata(gname)%></b>
<%
local g_selected = 0
for _, it in ipairs(items) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
// 更新总计
const totalSpan = document.getElementById("count-" + opt);
if (totalSpan) {
totalSpan.innerHTML = "<%:Selected:%> <span style='color:red;'>" + checked + " / " + cbs.length + "</span>";
totalSpan.innerHTML = "<%:Selected:%> <span style='color:red;'>" + checked + "/" + cbs.length + "</span>";
}
// 更新每个组计数
nodeList.querySelectorAll(".group-block").forEach(group => {
Expand Down Expand Up @@ -268,7 +268,14 @@
window.mv_nodeitem_rendered[cbid] = true;
searchInput.addEventListener("input", function() {
mv_filterGroups(searchInput, opt, nodeList);
})
});
searchInput.addEventListener('keydown', function(e) {
const isEnter = e.key === "Enter" || e.keyCode === 13;
if (!isEnter) return;
e.stopPropagation();
e.preventDefault();
searchInput.blur();
});
// checkbox 改变时更新计数
nodeList.addEventListener("change", () => {
mv_updateCount(opt, nodeList, searchInput);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
<%
local api = require "luci.passwall.api"
-%>
<style>
#cbi-<%=api.appname%>-socks .td.cbi-value-field > div {
white-space: nowrap;
}
@media (max-width: 1152px) {
#cbi-<%=api.appname%>-socks .td.cbi-value-field > div {
white-space: normal;
}
}
</style>
<script type="text/javascript">
//<![CDATA[
function go() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1652,14 +1652,46 @@
}

//]]></script>
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:From Share URL%>' onclick="return fromUrl(this, '<%=self.option%>', '<%=self.value%>')" />
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:Build Share URL%>' onclick="return exportUrl(this, '<%=self.option%>', '<%=self.value%>')" />
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:Generate QRCode%>' onclick="return genQrcode(this, '<%=self.option%>', '<%=self.value%>')" />
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:Export Config File%>' onclick="return exportConfigFile(this, '<%=self.value%>')" />
<div id="qrcode_div" style="margin-top: 1rem;display:none">
<div id="qrcode" style="display:flex;justify-content:center;"></div>
</div>
<div style="text-align:center;">
<span id="<%=self.option%>-status"></span>
<style>
.share-toolbar {
display: inline-block;
text-align: center;
padding: 5px 0 5px;
white-space: nowrap;
}
@media screen and (max-width: 500px) {
.share-toolbar {
white-space: normal;
}
}
.toolbar-buttons .cbi-button {
margin-right: 2px;
margin-bottom: 2px;
}
.toolbar-buttons .cbi-button:last-child {
margin-right: 0;
}
.status-text {
display: none;
}
.status-text:not(:empty) {
display: inline-block;
line-height: 1;
padding: 5px 0;
}
</style>
<div class="share-toolbar">
<div class="toolbar-buttons">
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:From Share URL%>' onclick="return fromUrl(this, '<%=self.option%>', '<%=self.value%>')" />
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:Build Share URL%>' onclick="return exportUrl(this, '<%=self.option%>', '<%=self.value%>')" />
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:Generate QRCode%>' onclick="return genQrcode(this, '<%=self.option%>', '<%=self.value%>')" />
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:Export Config File%>' onclick="return exportConfigFile(this, '<%=self.value%>')" />
</div>
<div id="qrcode_div" style="padding:5px 0;display:none">
<div id="qrcode" style="display:flex;justify-content:center;"></div>
</div>
<div style="text-align:center;padding:0;min-height:0;line-height:1;">
<span id="<%=self.option%>-status" class="status-text"></span>
</div>
</div>
<%+cbi/valuefooter%>
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,12 @@
}
}
}

function escape_html(s) {
return s.replace(/[&<>"']/g, c => ({
"&":"&amp;", "<":"&lt;", ">":"&gt;", '"':"&quot;", "'":"&#39;"
}[c]));
}
</script>

<script type="text/template" id="nodes-table-template">
Expand Down Expand Up @@ -938,7 +944,7 @@

tab_ul_li_html +=
'<li group_name="' + group + '" id="tab.passwall.nodes.' + group + '" class="cbi-tab">' +
'<a onclick="this.blur(); return cbi_t_switch(\'passwall.nodes\', \'' + group + '\')" href="<%=REQUEST_URI%>?tab.passwall.nodes=' + group + '">' + group_name + " | " + "<font style='color: red'>" + group_nodes[group].length + '</font></a>' +
'<a onclick="this.blur(); return cbi_t_switch(\'passwall.nodes\', \'' + group + '\')" href="<%=REQUEST_URI%>?tab.passwall.nodes=' + group + '">' + escape_html(group_name) + " | " + "<font style='color: red'>" + group_nodes[group].length + '</font></a>' +
'</li>'
tab_content_html +=
'<div class="cbi-tabcontainer" id="container.passwall.nodes.' + group + '" style="display: none;">' +
Expand Down
Loading