🍓 Sync 2026-08-11 20:54:16

This commit is contained in:
github-actions[bot]
2026-08-11 20:54:16 +08:00
parent b10bf57011
commit 030d9d82a4
23 changed files with 228 additions and 213 deletions
+2 -2
View File
@@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=UnblockNeteaseMusic
PKG_BASE_VERSION:=0.27.0
PKG_RELEASE:=2
PKG_RELEASE:=3
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/UnblockNeteaseMusic/server.git
PKG_SOURCE_DATE:=2022-10-09
PKG_SOURCE_VERSION:=39e21bfb4b7581f39785b190aeced201d23f0d41
PKG_SOURCE_VERSION:=c29ff1bfaf138afe3c41ee1be43daa91097afc9f
PKG_MIRROR_HASH:=skip
PKG_VERSION:=$(PKG_BASE_VERSION)-$(PKG_SOURCE_DATE)-$(call version_abbrev,$(PKG_SOURCE_VERSION))
+2 -2
View File
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
PKG_ARCH_BAIDUDRIVE:=$(ARCH)
PKG_NAME:=baidudrive
PKG_VERSION:=linkeasefull-runtime-v3.0.5
PKG_RELEASE:=7
PKG_VERSION:=baidudrive-runtime-v1.0.5
PKG_RELEASE:=8
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/baidudrive-runtime-v$(PKG_VERSION)/
PKG_HASH:=skip
+2 -2
View File
@@ -7,8 +7,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.8.10
PKG_RELEASE:=213
PKG_VERSION:=26.8.11
PKG_RELEASE:=214
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -471,7 +471,7 @@ function socks_status()
e.use_http = 0
if tonumber(use_http) > 0 then
e.use_http = 1
e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '/tmp/etc/passwall/bin/' | grep -v '_acl_' | grep '%s' | grep -E 'HTTP_|HTTP2SOCKS' > /dev/null", id)) == 0
e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep -E '\\+http|_http' > /dev/null", appname, id)) == 0
end
http_write_json(e)
end
@@ -64,8 +64,12 @@ uci -q delete passwall.@global_xray[0].route_only
for sid in $(uci show passwall | grep "=socks" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}'); do
if [ -z "$(echo ${sid} | grep '^socks_')" ]; then
new_id="socks_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 5)"
old_ref="Socks_${sid}"
uci -q rename passwall.${sid}="${new_id}"
unset sid new_id
for item in $(uci show passwall | grep "='${old_ref}'\$" | cut -d '=' -sf 1); do
uci -q set ${item}="${new_id}"
done
unset sid new_id old_ref item
fi
done
for sid in $(uci show passwall | grep "@acl_rule" | cut -d '.' -sf 2 | cut -d '=' -sf 1); do
@@ -407,7 +407,7 @@ run_socks() {
fi
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
json_add_string "local_http_address" "$bind"
json_add_string "local_http_port" "$http_port"
}
@@ -438,7 +438,7 @@ run_socks() {
sing-box)
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
local _args="http_address=$bind http_port=$http_port"
}
[ -n "$relay_port" ] && _args="${_args} server_host=$server_host server_port=$server_port"
@@ -448,7 +448,7 @@ run_socks() {
xray)
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
local _args="http_address=$bind http_port=$http_port"
}
[ -n "$relay_port" ] && _args="${_args} server_host=$server_host server_port=$server_port"
@@ -471,7 +471,7 @@ run_socks() {
ss-rust)
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
json_add_string "local_http_address" "$bind"
json_add_string "local_http_port" "$http_port"
}
@@ -487,7 +487,7 @@ run_socks() {
hysteria2)
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
json_add_string "local_http_address" "$bind"
json_add_string "local_http_port" "$http_port"
}
@@ -1003,7 +1003,7 @@ start_socks() {
local log=$(config_n_get $id log 1)
[ "$log" = "0" ] && log_file=""
local http_port=$(config_n_get $id http_port 0)
local http_config_file="HTTP2SOCKS_${id}.json"
local http_config_file="${flag}_http.json"
local enable_autoswitch=$(config_n_get $id enable_autoswitch 0)
local no_rec=0
[ "$enable_autoswitch" = "1" ] && no_rec=1
@@ -1020,16 +1020,16 @@ socks_node_switch() {
local flag new_node
eval_set_val "$@"
[ -n "$flag" ] && [ -n "$new_node" ] && {
local prefix pf filename
local suffix pf filename
# 结束 SS 插件进程
for prefix in "" "HTTP_"; do
pf="$TMP_PATH/${prefix}${flag}_plugin.pid"
for suffix in "" "+http"; do
pf="$TMP_PATH/${flag}${suffix}_plugin.pid"
[ -s "$pf" ] && kill -9 "$(head -n1 "$pf")" >/dev/null 2>&1
done
busybox pgrep -af "$TMP_BIN_PATH" | awk -v P1="${flag}" 'BEGIN{IGNORECASE=1}$0~P1 && !/acl\/|acl_/{print $1}' | xargs kill -9 >/dev/null 2>&1
for prefix in "" "HTTP_" "HTTP2"; do
rm -rf "$TMP_PATH/${prefix}${flag}"*
for suffix in "" "+http" "_http"; do
rm -rf "$TMP_PATH/${flag}${suffix}"*
done
for filename in $(ls ${TMP_SCRIPT_FUNC_PATH}); do
@@ -1045,7 +1045,7 @@ socks_node_switch() {
local log=$(config_n_get $flag log 1)
[ "$log" = "0" ] && log_file=""
local http_port=$(config_n_get $flag http_port 0)
local http_config_file="HTTP2SOCKS_${flag}.json"
local http_config_file="${flag}_http.json"
LOG_FILE="/dev/null"
run_socks flag=$flag node=$new_node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file
set_cache_var "${flag}" "$new_node"
@@ -236,15 +236,15 @@ load_acl() {
[ -n "$(get_cache_var "ACL_${sid}_dns_port")" ] && dns_redirect_port=$(get_cache_var "ACL_${sid}_dns_port")
[ -n "$(get_cache_var "ACL_${sid}_fakedns")" ] && use_fakedns=$(get_cache_var "ACL_${sid}_fakedns")
[ -n "$tcp_node" ] && {
if is_socks_wrap "$tcp_node"; then
tcp_node_remark="Socks 配置($(config_n_get ${tcp_node#Socks_} port) 端口)"
if [ "$(config_get_type $tcp_node)" = "socks" ]; then
tcp_node_remark="Socks 配置($(config_n_get $tcp_node port) 端口)"
else
tcp_node_remark=$(config_n_get $tcp_node remarks)
fi
}
[ -n "$udp_node" ] && {
if is_socks_wrap "$udp_node"; then
udp_node_remark="Socks 配置($(config_n_get ${udp_node#Socks_} port) 端口)"
if [ "$(config_get_type $udp_node)" = "socks" ]; then
udp_node_remark="Socks 配置($(config_n_get $udp_node port) 端口)"
else
udp_node_remark=$(config_n_get $udp_node remarks)
fi
@@ -256,13 +256,13 @@ load_acl() {
[ -n "$udp_node" ] && [ "$(config_n_get $udp_node protocol)" = "_shunt" ] && use_shunt_udp=1
[ "${use_global_config}" = "1" ] && {
if is_socks_wrap "$TCP_NODE"; then
tcp_node_remark="Socks 配置($(config_n_get ${TCP_NODE#Socks_} port) 端口)"
if [ "$(config_get_type $TCP_NODE)" = "socks" ]; then
tcp_node_remark="Socks 配置($(config_n_get $TCP_NODE} port) 端口)"
else
tcp_node_remark=$(config_n_get $TCP_NODE remarks)
fi
if is_socks_wrap "$UDP_NODE"; then
udp_node_remark="Socks 配置($(config_n_get ${UDP_NODE#Socks_} port) 端口)"
if [ "$(config_get_type $UDP_NODE)" = "socks" ]; then
udp_node_remark="Socks 配置($(config_n_get $UDP_NODE port) 端口)"
else
udp_node_remark=$(config_n_get $UDP_NODE remarks)
fi
@@ -651,8 +651,8 @@ load_acl() {
# 加载TCP默认代理模式
if [ -n "${TCP_PROXY_MODE}" ]; then
[ -n "$TCP_NODE" ] && {
if is_socks_wrap "$TCP_NODE"; then
msg2="${msg}使用 TCP 节点[Socks 配置($(config_n_get ${TCP_NODE#Socks_} port) 端口)]"
if [ "$(config_get_type $TCP_NODE)" = "socks" ]; then
msg2="${msg}使用 TCP 节点[Socks 配置($(config_n_get $TCP_NODE port) 端口)]"
else
msg2="${msg}使用 TCP 节点[$(config_n_get $TCP_NODE remarks)]"
fi
@@ -710,8 +710,8 @@ load_acl() {
# 加载UDP默认代理模式
if [ -n "${UDP_PROXY_MODE}" ]; then
[ -n "$UDP_NODE" ] || [ "$TCP_UDP" = "1" ] && {
if is_socks_wrap "$UDP_NODE"; then
msg2="${msg}使用 UDP 节点[Socks 配置($(config_n_get ${UDP_NODE#Socks_} port) 端口)](TPROXY:${UDP_REDIR_PORT})"
if [ "$(config_get_type $UDP_NODE)" = "socks" ]; then
msg2="${msg}使用 UDP 节点[Socks 配置($(config_n_get $UDP_NODE port) 端口)](TPROXY:${UDP_REDIR_PORT})"
else
msg2="${msg}使用 UDP 节点[$(config_n_get $UDP_NODE remarks)](TPROXY:${UDP_REDIR_PORT})"
fi
@@ -296,15 +296,15 @@ load_acl() {
[ -n "$(get_cache_var "ACL_${sid}_dns_port")" ] && dns_redirect_port=$(get_cache_var "ACL_${sid}_dns_port")
[ -n "$(get_cache_var "ACL_${sid}_fakedns")" ] && use_fakedns=$(get_cache_var "ACL_${sid}_fakedns")
[ -n "$tcp_node" ] && {
if is_socks_wrap "$tcp_node"; then
tcp_node_remark="Socks 配置($(config_n_get ${tcp_node#Socks_} port) 端口)"
if [ "$(config_get_type $tcp_node)" = "socks" ]; then
tcp_node_remark="Socks 配置($(config_n_get $tcp_node port) 端口)"
else
tcp_node_remark=$(config_n_get $tcp_node remarks)
fi
}
[ -n "$udp_node" ] && {
if is_socks_wrap "$udp_node"; then
udp_node_remark="Socks 配置($(config_n_get ${udp_node#Socks_} port) 端口)"
if [ "$(config_get_type $udp_node)" = "socks" ]; then
udp_node_remark="Socks 配置($(config_n_get $udp_node port) 端口)"
else
udp_node_remark=$(config_n_get $udp_node remarks)
fi
@@ -315,13 +315,13 @@ load_acl() {
[ -n "$udp_node" ] && [ "$(config_n_get $udp_node protocol)" = "_shunt" ] && use_shunt_udp=1
[ "${use_global_config}" = "1" ] && {
if is_socks_wrap "$TCP_NODE"; then
tcp_node_remark="Socks 配置($(config_n_get ${TCP_NODE#Socks_} port) 端口)"
if [ "$(config_get_type $TCP_NODE)" = "socks" ]; then
tcp_node_remark="Socks 配置($(config_n_get $TCP_NODE port) 端口)"
else
tcp_node_remark=$(config_n_get $TCP_NODE remarks)
fi
if is_socks_wrap "$UDP_NODE"; then
udp_node_remark="Socks 配置($(config_n_get ${UDP_NODE#Socks_} port) 端口)"
if [ "$(config_get_type $UDP_NODE)" = "socks" ]; then
udp_node_remark="Socks 配置($(config_n_get $UDP_NODE port) 端口)"
else
udp_node_remark=$(config_n_get $UDP_NODE remarks)
fi
@@ -709,8 +709,8 @@ load_acl() {
# 加载TCP默认代理模式
if [ -n "${TCP_PROXY_MODE}" ]; then
[ -n "$TCP_NODE" ] && {
if is_socks_wrap "$TCP_NODE"; then
msg2="${msg}使用 TCP 节点[Socks 配置($(config_n_get ${TCP_NODE#Socks_} port) 端口)]"
if [ "$(config_get_type $TCP_NODE)" = "socks" ]; then
msg2="${msg}使用 TCP 节点[Socks 配置($(config_n_get $TCP_NODE port) 端口)]"
else
msg2="${msg}使用 TCP 节点[$(config_n_get $TCP_NODE remarks)]"
fi
@@ -773,8 +773,8 @@ load_acl() {
# 加载UDP默认代理模式
if [ -n "${UDP_PROXY_MODE}" ]; then
[ -n "$UDP_NODE" ] || [ "$TCP_UDP" = "1" ] && {
if is_socks_wrap "$UDP_NODE"; then
msg2="${msg}使用 UDP 节点[Socks 配置($(config_n_get ${UDP_NODE#Socks_} port) 端口)](TPROXY:${UDP_REDIR_PORT})"
if [ "$(config_get_type $UDP_NODE)" = "socks" ]; then
msg2="${msg}使用 UDP 节点[Socks 配置($(config_n_get $UDP_NODE port) 端口)](TPROXY:${UDP_REDIR_PORT})"
else
msg2="${msg}使用 UDP 节点[$(config_n_get $UDP_NODE remarks)](TPROXY:${UDP_REDIR_PORT})"
fi
+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.8.10
PKG_RELEASE:=77
PKG_RELEASE:=78
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -370,7 +370,7 @@ function socks_status()
e.use_http = 0
if tonumber(use_http) > 0 then
e.use_http = 1
e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep -E 'HTTP_|HTTP2SOCKS' > /dev/null", appname, id)) == 0
e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep -E '\\+http|_http' > /dev/null", appname, id)) == 0
end
http_write_json(e)
end
@@ -68,11 +68,15 @@ frag_dly=$(uci -q get passwall2.@global_xray[0].fragment_delay)
[ -n "$frag_dly" ] && uci -q set passwall2.@global_xray[0].fragment_delays="$frag_dly" && uci -q delete passwall2.@global_xray[0].fragment_delay
for sid in $(uci show passwall2 | grep "=socks" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}'); do
if [ -z "$(echo ${sid} | grep '^socks_')" ]; then
new_id="socks_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 5)"
uci -q rename passwall2.${sid}="${new_id}"
unset sid new_id
fi
if [ -z "$(echo ${sid} | grep '^socks_')" ]; then
new_id="socks_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 5)"
old_ref="Socks_${sid}"
uci -q rename passwall2.${sid}="${new_id}"
for item in $(uci show passwall2 | grep "='${old_ref}'\$" | cut -d '=' -sf 1); do
uci -q set ${item}="${new_id}"
done
unset sid new_id old_ref item
fi
done
for sid in $(uci show passwall2 | grep "@acl_rule" | cut -d '.' -sf 2 | cut -d '=' -sf 1); do
new_id="acl_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 5)"
@@ -402,7 +402,7 @@ run_socks() {
sing-box)
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
json_add_string "local_http_address" "${bind}"
json_add_string "local_http_port" "${http_port}"
}
@@ -430,7 +430,7 @@ run_socks() {
xray)
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
json_add_string "local_http_address" "${bind}"
json_add_string "local_http_port" "${http_port}"
}
@@ -474,7 +474,7 @@ run_socks() {
json_add_string "local_socks_port" "${socks_port}"
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
json_add_string "local_http_address" "${bind}"
json_add_string "local_http_port" "${http_port}"
}
@@ -491,7 +491,7 @@ run_socks() {
json_add_string "local_socks_port" "${socks_port}"
[ "$http_port" != "0" ] && {
http_flag=1
config_file="${config_file//SOCKS/HTTP_SOCKS}"
config_file="${config_file%%.*}+http${config_file#${config_file%%.*}}"
json_add_string "local_http_address" "${bind}"
json_add_string "local_http_port" "${http_port}"
}
@@ -535,16 +535,16 @@ socks_node_switch() {
local flag new_node
eval_set_val $@
[ -n "$flag" ] && [ -n "$new_node" ] && {
local prefix pf filename
local suffix pf filename
# Kill the SS plugin process
for prefix in "" "HTTP_"; do
pf="$TMP_PATH/${prefix}${flag}_plugin.pid"
for suffix in "" "+http"; do
pf="$TMP_PATH/${flag}${suffix}_plugin.pid"
[ -s "$pf" ] && kill -9 "$(head -n1 "$pf")" >/dev/null 2>&1
done
busybox pgrep -af "$TMP_BIN_PATH" | awk -v P1="${flag}" 'BEGIN{IGNORECASE=1}$0~P1 && !/acl\/|acl_/{print $1}' | xargs kill -9 >/dev/null 2>&1
for prefix in "" "HTTP_" "HTTP2"; do
rm -rf "$TMP_PATH/${prefix}${flag}"*
for suffix in "" "+http" "_http"; do
rm -rf "$TMP_PATH/${flag}${suffix}"*
done
for filename in $(ls ${TMP_SCRIPT_FUNC_PATH}); do
@@ -560,7 +560,7 @@ socks_node_switch() {
local log=$(config_n_get $flag log 1)
[ "$log" == "0" ] && log_file=""
local http_port=$(config_n_get $flag http_port 0)
local http_config_file="HTTP2SOCKS_${flag}.json"
local http_config_file="${flag}_http.json"
LOG_FILE="/dev/null"
run_socks flag=$flag node=$new_node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file
set_cache_var "${flag}" "$new_node"
@@ -727,7 +727,7 @@ start_socks() {
local log=$(config_n_get $id log 1)
[ "$log" == "0" ] && log_file=""
local http_port=$(config_n_get $id http_port 0)
local http_config_file="HTTP2SOCKS_${id}.json"
local http_config_file="${id}_http.json"
run_socks flag=$id node=$node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file
set_cache_var "${id}" "$node"
+1 -1
View File
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=196
PKG_RELEASE:=55
PKG_RELEASE:=56
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
@@ -519,7 +519,7 @@ local function anytls_to_mihomo_node(entry)
return nil
end
return {
local result = {
type = "anytls",
alias = entry.name,
raw_alias = entry.name,
@@ -530,6 +530,12 @@ local function anytls_to_mihomo_node(entry)
insecure = entry.allow_insecure and "1" or "0",
fingerprint = entry.client_fingerprint
}
local saved_alias = result.alias
result.alias = nil
result.hashkey = md5(jsonStringify(result) .. "_" .. (saved_alias or ""))
result.alias = saved_alias
return result
end
local function split_csv_values(value)
+1 -1
View File
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
FOOTSTRAP_VERSION?=
ifneq ($(FOOTSTRAP_VERSION),)
PKG_VERSION:=$(FOOTSTRAP_VERSION)
PKG_RELEASE:=11
PKG_RELEASE:=12
endif
LUCI_TITLE:=Footstrap Theme
@@ -381,33 +381,6 @@ function seed() {
adoptEntry();
}
/* Does this document already carry the stylesheet a menu.d node names?
*
* The value is a path under /luci-static/resources (`view/foo/foo.css`), and head.ut prints it with
* a `?v=` cache key, so compare the PATH only and by suffix the theme does not get to assume the
* resource base, and a re-hosted sheet keeps its element and its href (fs-sheets.js disables the
* <link>, it never removes it), which is exactly the "already here" this asks about. */
function documentHasSheet(css) {
const want = '/' + css.replace(/^\/+/, '');
for (const link of document.querySelectorAll('link[rel~="stylesheet"][href]')) {
/* A <link> INSIDE the view tree dies with the swap it is the one shape fs-sheets.js needs
* no handling for, and every scan there skips it for the same reason (VIEW_SHEETS, and the
* `closest('#view')` guards in documentPoisoned/scopeToCurrentPage/dedupeViewSheets).
* Counting it here would answer the wrong question: `luci-app-nlbwmon` returns
* `E('link', { rel: 'stylesheet', href: L.resource('view/nlbw.css') })` from render(), so
* standing on that page its sheet IS in the document and it is exactly the sheet the next
* dom.content() throws away. An app in that shape moving to a menu.d `css` is the migration
* #8920 was written for, so the guard has to survive it: leaving from that page must be a
* full load, not a swap into an unstyled document. */
if (link.closest('#view'))
continue;
const href = (link.getAttribute('href') || '').split('?')[0];
if (href.endsWith(want))
return true;
}
return false;
}
/* Attempt an in-place navigation to `pathname`. Returns true if handled as a
* SPA nav (caller should preventDefault), false to let the browser do a normal
* full navigation. `push` adds a history entry (false when replaying popstate).
@@ -432,25 +405,29 @@ function navigate(pathname, push, kbd) {
/* A PAGE WHOSE STYLESHEET ONLY THE SERVER CAN EMIT IS NOT OURS TO SWAP INTO.
*
* A menu.d node may name its own sheet (`"css": "view/foo/foo.css"`), and the server links it
* from <head> on a full load (partials/head.ut). Nothing here can: a swap replaces #view's
* children, it does not re-render a document, so reaching such a page by CLICK would show it
* with the app's CSS missing while reaching the same page by URL or F5 showed it styled. One
* page, two appearances, decided by how the user got there.
* A menu.d node may name its own sheet (`"css": "view/foo/foo.css"`), and the server links it from
* <head> on a full load (partials/head.ut). Nothing here can: a swap replaces #view's children, it
* does not re-render a document, so reaching such a page by CLICK would show it with the app's CSS
* missing while reaching the same page by URL or F5 showed it styled. One page, two appearances,
* decided by how the user got there.
*
* So decline, exactly as the poisoned-document bail above does: speed is traded for
* correctness, never the other way. It costs ONE full load per such page after it the <link>
* is in the document, this test passes, and every later visit is a swap again (fs-sheets.js
* owns the sheet from then on and re-lights it per page).
* So decline, exactly as the poisoned-document bail above does: speed is traded for correctness,
* never the other way. It costs ONE full load per such page after it the <link> is in the
* document, this test passes, and every later visit is a swap again (fs-sheets.js owns the sheet
* from then on and re-lights it per page).
*
* Injecting the <link> here instead would work and is deliberately not done: it would put the
* theme in charge of fetching and ordering a foreign stylesheet, which is the job fs-sheets.js
* exists to keep out of the theme. The server already does it correctly.
* Injecting the <link> here instead would work and is deliberately not done: it would put the theme
* in charge of fetching and ordering a foreign stylesheet, which is the job fs-sheets.js exists to
* keep out of the theme. The server already does it correctly.
*
* `node.css` reaches the client because /admin/menu serves the dispatcher's own tree and
* ui.js's scrubMenu() only rewrites `satisfied`. On a luci-base that predates the `css` schema
* entry the property is dropped server-side, so this is simply never true there. */
if (typeof node.css === 'string' && node.css !== '' && !documentHasSheet(node.css))
* The QUESTION is fs-sheets.js's, not this module's: which sheets a document carries, and which of
* them a swap is about to delete, is the one thing that file knows see documentCarries(). What is
* decided here is only what to do about the answer.
*
* `node.css` reaches the client because /admin/menu serves the dispatcher's own tree and ui.js's
* scrubMenu() only rewrites `satisfied`. On a luci-base that predates the `css` schema entry the
* property is dropped server-side, so this is simply never true there. */
if (typeof node.css === 'string' && node.css !== '' && !sheets.documentCarries(node.css))
return false;
const rsegs = res.segs;
@@ -307,6 +307,37 @@ function judgeSheet(el, universe) {
* <link> INSIDE the view tree (`luci-app-nlbwmon`) needs no handling: it dies with the swap. */
const VIEW_SHEETS = 'style:not([data-fs-shell]), link[rel~="stylesheet"]:not([data-fs-shell])';
/* DOES THIS SHEET OUTLIVE THE PAGE IT ARRIVED WITH? Everything this module decides hangs off that
* one question, and the answer is where the element sits: a <style>/<link> inside the view tree
* dies with the swap (dom.content() replaces #view's children), so it can neither poison the next
* page, nor need scoping to this one, nor be a duplicate worth removing. Only sheets outside it
* are this module's business. Named because it is asked in four places and read wrong in none of
* them only by luck: `!el.closest('#view')` states where an element is, not what follows from it. */
function outlivesPage(el) {
return !el.closest('#view');
}
/* Is `path` a menu.d node's `css`, i.e. a path under /luci-static/resources already carried by
* this document in a form that SURVIVES a swap?
*
* The router asks before committing a client navigation: only a server render emits that <link>, so
* a page whose stylesheet is missing must arrive by full load (see fs-router.js). The question is
* this module's because the answer is: a link inside #view is about to be deleted with the rest of
* the view, and counting it would hand the router a sheet the next dom.content() throws away
* `luci-app-nlbwmon` returns E('link', , L.resource('view/nlbw.css')) from render(), so that shape
* is real. Compared by PATH and by suffix: head.ut prints `{{ resource }}/{{ dispatched.css }}?v=…`,
* so neither the resource base nor the cache key is anyone's to assume. */
function documentCarries(path) {
const want = '/' + String(path).replace(/^\/+/, '');
for (const link of document.querySelectorAll('link[rel~="stylesheet"][href]')) {
if (!outlivesPage(link))
continue;
if ((link.getAttribute('href') || '').split('?')[0].endsWith(want))
return true;
}
return false;
}
/* An invasive sheet we OWN is contained scopeToCurrentPage() darkens it the moment the router
* stamps the new page, so it cannot reach the next page and the document is not spent. One we could
* not attribute (not re-hostable, so never owned: an @import at the top, a sheet built with
@@ -331,7 +362,7 @@ function documentPoisoned() {
const names = themeNames();
return Array.prototype.some.call(
document.querySelectorAll(VIEW_SHEETS),
(el) => !el.closest('#view')
(el) => outlivesPage(el)
&& (!names || (invasiveSheet(el, names) && !_owner.has(el) && !_silenced.has(el))));
}
@@ -623,7 +654,7 @@ function scopeToCurrentPage(segs) {
if (segs) _curKey = appKey(segs);
const key = currentKey();
document.querySelectorAll(VIEW_SHEETS).forEach((el) => {
if (el.closest('#view') || !_owner.has(el)) return;
if (!outlivesPage(el) || !_owner.has(el)) return;
setEnabled(el, _owner.get(el) === key);
});
}
@@ -731,7 +762,7 @@ function sheetKey(el) {
function dedupeViewSheets() {
const seen = new Set();
document.querySelectorAll(VIEW_SHEETS).forEach((el) => {
if (el.closest('#view')) return;
if (!outlivesPage(el)) return;
const key = sheetKey(el);
if (key === null) return;
if (seen.has(key)) el.remove();
@@ -841,6 +872,7 @@ function watchViewSheets() {
}
return baseclass.extend({
documentCarries,
documentPoisoned,
scopeToCurrentPage,
watchViewSheets
@@ -60,6 +60,13 @@
--fs-focus-ring: 0 0 0 3px var(--fs-accent-soft);
--fs-focus-ring-solo: 0 0 0 2px var(--fs-panel), 0 0 0 5px var(--fs-accent);
--fs-focus-ring-invalid: 0 0 0 3px var(--fs-danger-fill);
/* THE CHOSEN-ROW RAIL, the same ladder one rung along: a chosen row in a list says so with an
* opaque surface and an inset bar, never with a tint of the accent under accent text (that
* measured 4.21:1 in dark see theme/65-dropdown.css). Named because it is written in three
* places that cannot be one rule: the open dropdown's row, a native `select option:checked`,
* and the ring the FOCUSED row wears on top of it. The first two are pinned to each other by
* tools/mirror.mjs; the third restated the literal until it had a name. */
--fs-row-rail: inset 2px 0 0 var(--fs-accent);
/* the theme's ONE colour literal, deliberately: black at .7 is the absence of light
* behind a dialog, not a tint of any token a palette-tinted scrim over a dark page
+47 -63
View File
@@ -1,32 +1,34 @@
@layer page {
/* THE FILTER BAR, which LuCI writes as a run of inline text.
*
* `tools/views.js` (LogreadBox, shared by System Log, Kernel Log and every third-party page
* built on it) emits two bare `<div style="margin-bottom:10px">`, each holding label / control
* pairs with nothing around a pair: `<label>Не</label><input type=checkbox><label>объект:</label>
* <select>`. Inline flow then breaks wherever the line runs out, and on a phone that is between
* a label and the control it names reported from a 390px screen, where the second `Не` sat
* alone at the end of one line, `уровень:` opened the next one without it, and `Макс. строк:`
* broke INSIDE the label, leaving `Макс.` on one line and `строк:` on the next with the field it
* belongs to.
* Both log views build one: `tools/views.js` (LogreadBox the System Log, and every third-party
* page built on it) and `view/status/dmesg.js` (the Kernel Log). Each emits bare
* `<div style="margin-bottom:10px">` rows holding label / control pairs with nothing around a
* pair: `<label>Не</label><input type=checkbox><label>объект:</label><select>`. Inline flow then
* breaks wherever the line runs out, and on a phone that is between a label and the control it
* names reported from a 390px screen, where the second `Не` sat alone at the end of one line,
* `уровень:` opened the next one without it, and `Макс. строк:` broke INSIDE the label, leaving
* `Макс.` above `строк:` and the field they belong to.
*
* Flex with wrapping fixes both halves: a wrap can now only happen BETWEEN items, and a label is
* one unbreakable item, so a pair still separates at worst onto two lines instead of a label
* cutting in half. The fields shrink instead of forcing that: `min-width: 0` undoes the field
* box in theme/60-inputs.css for this row only.
* ONE RULE OF LAYOUT, TWO TIERS, NO PER-FIELD EXCEPTIONS. The rows are flex, so a wrap can only
* fall BETWEEN items and a label is one unbreakable item. A field may SHRINK to share the line
* (`min-width: 0` undoes the field box in theme/60-inputs.css for these rows) but none of them
* GROWS past its own width that is what keeps `Макс. строк:` a small box beside a wide search
* field without anything having to name it. The one exception states itself: the free-text filter
* is the only field whose content has no length, so it takes the leftover space.
*
* Keyed on the ROW's SHAPE, not on the page and not on the controls' ids. `data-page` differs
* between the system and the kernel log and a third-party LogreadBox has neither but the ids
* are no better: only the System Log is built by `tools/views.js`, and Status Kernel Log is a
* view of its own (`view/status/dmesg.js`) whose four rows carry `logFromTime`, `logToTime`,
* `invertLogRangeTime`, `invertSeverity` and `invertAscendingSort`, of which an id list written
* against LogreadBox catches none `logFacilitySelect` appears there only as a `for=`, on a
* label whose control is a time field. Both views wrap their rows in `#content_syslog`, and a
* filter row is exactly one that opens with a `<label>`; the scroll-to-end button's row and the
* log itself do not. That shape holds for any view built the same way.
* `input:not([type])` IS that filter, in both views and in that order of certainty: every other
* input under `#content_syslog` states a type (`checkbox` for the four inverts and the sort flag,
* `number` for Max rows and for the Kernel Log's boot-time range), and both views leave the text
* one untyped. Nothing here keys on an id, so a third-party LogreadBox gets the same layout, and
* no rule has to know how many fields a row holds.
*
* The inline `margin: 0 5px` LuCI writes on the labels stays as the horizontal spacing only
* the ROW gap is ours, so nothing here has to fight an inline style. */
* Scoped by SHAPE for the same reason: `data-page` differs between the two logs and a third-party
* box has neither, while both wrap their rows in `#content_syslog` and a filter row is exactly one
* that opens with a `<label>` the scroll-to-end button's row and the log itself do not.
*
* The inline `margin: 0 5px` LuCI writes on the labels stays as the horizontal spacing only the
* ROW gap is ours, so nothing here has to fight an inline style. */
#content_syslog > div:has(> label) {
display: flex;
flex-wrap: wrap;
@@ -35,50 +37,32 @@
}
#content_syslog > div:has(> label) > label { white-space: nowrap; }
#content_syslog > div:has(> label) > :is(select, input) {
flex: 1 1 auto;
/* both halves are needed and they answer different layouts: `min-width: 0` lets a FLEX item
* shrink past its content, `max-width: 100%` clamps a GRID item to its track. A <select>
* carries the theme's own 210px field width, which a `minmax(0, 1fr)` track does not
* override measured at 360px, where both selects hung 3px past the view. */
/* shrink, never grow. `max-width` is not the same lever as `min-width` and both are needed:
* one lets a flex item go below its content, the other clamps a `<select>` that carries the
* theme's own 210px width measured at 360px, where both selects hung 3px past the view. */
flex: 0 1 auto;
min-width: 0;
max-width: 100%;
}
/* a checkbox is not a field: left to its own 16px, it would otherwise flex to fill the line. */
#content_syslog > div:has(> label) > input[type="checkbox"] { flex: 0 0 auto; }
/* The max-rows box keeps a sane ceiling: as wide as the free-text filter it reads as a second
* search field. Selected as the row's `number` input, not by its id: `#logMaxRows` alone is
* (1,0,0) and the field rule above is (1,0,3), so the id LOSES and re-anchoring it under
* `#content_syslog` would put two ids in one selector, [2,0,x], past the [1,7,0] ceiling
* `npm run css-metrics` holds this sheet to. `[type="number"]` is the only one in either log's
* filter bar and it out-specifies at (1,1,3). */
#content_syslog > div:has(> label) > input[type="number"] { flex-basis: 6ch; max-width: 12ch; }
/* ON A PHONE, ONE FILTER PER LINE. Wrapping alone still fills each line greedily, so the second
* `Не` rode the end of the first line while the `уровень:` it inverts opened the next one. The
* first row is a fixed alternation of exactly two filters invert label, checkbox, name label,
* control, twice so a four-column grid puts one filter on each line.
*
* EVERY OTHER ROW is deliberately left to the wrapping above, because none of them is that
* shape. The System Log's second row holds a checkbox filter beside a bare `Макс. строк:` pair,
* and the same four tracks are then sized by items that are not each other's counterparts:
* measured, the max-rows box defined column 2 and pushed `содержит:` and its field off the card.
* The Kernel Log's rows are three different shapes again a range with two time fields, a
* severity select with a trailing `and above`, and a lone sort checkbox. Wrapping puts each of
* those on as many lines as it needs, which is all the grid was ever for; the grid is worth its
* one selector only where the alternation is exact. */
@container fs-view (max-width: 560px) {
/* THE ROW WITH TWO SELECTS, which is the same row `#logFacilitySelect` names by shape
* because neither spelling of the id survives the arithmetic: `div:has(> #logFacilitySelect)`
* is (1,0,1) and loses to the flex rule above at (1,0,2), while `#content_syslog >` in front
* of it is two ids, [2,0,1], past the [1,7,0] ceiling `npm run css-metrics` holds. Two
* sibling selects is what the `auto auto auto minmax(0, 1fr)` template is sized for eight
* children, two filters and it is unique to the System Log's first row: the Kernel Log's
* severity row carries one select, its range row two time fields. (1,0,4) beats the flex
* rule on specificity, not on source order. */
#content_syslog > div:has(> label):has(> select ~ select) {
display: grid;
grid-template-columns: auto auto auto minmax(0, 1fr);
column-gap: var(--fs-space-1);
/* THE TWO TIERS ARE DISJOINT CONTAINER QUERIES, not a base rule and an override: each states one
* layout in full, so neither can win or lose against the other by source order the trap the
* first version of this file fell into, where a later generalisation silently out-specified two
* rules that were meant to override it. */
@container fs-view (width > 560px) {
/* room for a filter and a half at least: the search box takes what the named fields leave. */
#content_syslog > div:has(> label) > input:not([type]) { flex: 1 1 12rem; }
}
@container fs-view (width <= 560px) {
/* ONE FIELD PER LINE, its labels on the line above it. Wrapping alone fills each line greedily,
* so the second `Не` rode the end of the first line while the `уровень:` it inverts opened the
* next one and no flex rule can bind a label to the field that follows it. A full-width basis
* can: the field always starts a line, so whatever labels precede it sit together above it. It
* costs one line per filter on a phone and holds for every row of both views the range pair,
* the severity select with its trailing `and above`, the lone sort checkbox without any of
* them being named or counted. A checkbox is not a field and keeps its 16px. */
#content_syslog > div:has(> label) > :is(select, input:not([type="checkbox"])) {
flex-basis: 100%;
}
}
@@ -21,50 +21,50 @@
}
.cbi-progressbar::after {
content: attr(title);
position: absolute; inset-inline: auto 0; bottom: calc(100% + 4px); top: auto;
/* the gap is a scale step, not a literal 4: every other gap in the theme scales with the
* density axis and this one sat outside it. Identical at the default density. */
position: absolute; inset-inline: auto 0; bottom: calc(100% + var(--fs-space-1)); top: auto;
font-family: var(--fs-font-mono); font-size: var(--fs-type-xs); font-weight: var(--fs-weight-normal);
color: var(--fs-dim); white-space: nowrap; text-shadow: none;
}
/* THE BAR IS THE FIELD OF A LABELLED ROW put the value BESIDE it, not above it.
/* THE VALUE SITS BESIDE THE BAR wherever the bar already shares its line with a name.
*
* The placement above the bar (and the stock one it replaced: bootstrap floats the same title
* at `top: -1.4em; left: 0`) assumes the shape the Overview has, where the bar owns a full-width
* line and its name sits on the line above there the value lands at the far end of the name's
* own line and the two read as one row. A `.cbi-value` row is the other shape: the name is
* already in the LEFT column, on the bar's own line, so a value floating above the bar has
* nothing to pair with. It lands against the row divider instead, nearer the row above than the
* bar it belongs to reported against luci-app-modeminfo's Antennas page, three of these rows
* per card and every dBm figure reading as though it belonged to the meter above it.
* The default above puts it over the bar's far edge, and that assumes the shape the Overview has:
* the meter owns a full-width line and its name sits on the line above, so the value lands at the
* far end of that name's own line and the two read as one row. (Stock bootstrap assumes the same,
* floating the title at `top: -1.4em; left: 0`.) Two shapes are not that, and they are not each
* other either CSS cannot ask "does this row carry its own label" so each is named:
*
* Inline, then: the row becomes `name | meter | value`, one line, values aligned to a single
* column because the reserve is fixed rather than shrink-wrapped. The reserve is
* `2 x --fs-space-10` because the value can be `-140.0 dBm`, half again as wide as the
* `100% (1.2 GiB)` a single step covers in the data-table rule below.
* `.cbi-value-field > .cbi-progressbar` the meter IS the field of a CBI form row, an app
* building `.cbi-value` with ui.js. Reported against luci-app-modeminfo's Antennas page:
* three such rows per card, every dBm figure reading as though it belonged to the meter above
* it, because the value lands against the row divider rather than beside its own bar.
* `.table:has(.tr.table-titles) .td ` a meter COLUMN in a multi-column data table
* (luci-app-cpu-status's per-CPU load), where the floated value lands on the row divider too.
*
* Not the same selector as that rule and deliberately so: this is the CBI FORM row (an app
* building a `.cbi-value` with ui.js), that one is a multi-column data table. Both end up
* inline-right; neither can be expressed as the other, because CSS cannot ask "does this row
* carry its own label". The Overview and the package manager keep the value above the bar:
* their meters sit in a `.td` and a bare `.controls` div, neither of which is a field. */
.cbi-value-field > .cbi-progressbar { margin-inline-end: calc(var(--fs-space-10) * 2); }
.cbi-value-field > .cbi-progressbar::after {
* Both want the same geometry and differ in ONE number, so the geometry is written once and the
* number is the knob: `--fs-meter-value` is the room the out-of-flow value needs. A form row holds
* `-140.0 dBm`, half again as wide as the `100% (1.2 GiB)` of a table cell. The Overview and the
* package manager keep the value above the bar their meters sit in a `.td` and a bare
* `.controls` div, which is neither shape. */
.cbi-value-field > .cbi-progressbar,
.cbi-section .table:has(.tr.table-titles) .td .cbi-progressbar {
margin-inline-end: var(--fs-meter-value);
}
.cbi-value-field > .cbi-progressbar::after,
.cbi-section .table:has(.tr.table-titles) .td .cbi-progressbar::after {
inset-inline: calc(100% + var(--fs-space-2)) auto; bottom: auto; top: 50%;
transform: translateY(-50%);
}
.cbi-value-field > .cbi-progressbar { --fs-meter-value: calc(var(--fs-space-10) * 2); }
.cbi-section .table:has(.tr.table-titles) .td .cbi-progressbar {
--fs-meter-value: var(--fs-space-10);
/* narrower than this it stops reading as a meter at all; the column gives up the rest. */
min-width: 60px;
}
/* Progressbar rows in a section table (luci-app-cpu-status): keep the native label | bar
* row, just centre the cells vertically. */
.cbi-section .table:not([id]):not(.cbi-section-table) .tr:has(.cbi-progressbar) .td { vertical-align: middle; }
/* Multi-column DATA tables with a progressbar column (cpu-status per-CPU load): the
* generic meter floats its value ABOVE the bar, which here lands on the row divider.
* Shrink the bar and put the value INLINE to its right, clear of the border. */
.cbi-section .table:has(.tr.table-titles) .td .cbi-progressbar {
min-width: 60px; margin-inline-end: var(--fs-space-10);
}
.cbi-section .table:has(.tr.table-titles) .td .cbi-progressbar::after {
inset-inline: calc(100% + 8px) auto; bottom: auto; top: 50%;
transform: translateY(-50%);
}
}
@@ -429,7 +429,7 @@
* under AA the tint of the accent eats its own contrast. */
.cbi-value-field select option:checked {
/* @mirror selected-row/paint */
background: var(--fs-panel2); color: var(--fs-accent); box-shadow: inset 2px 0 0 var(--fs-accent);
background: var(--fs-panel2); color: var(--fs-accent); box-shadow: var(--fs-row-rail);
/* @endmirror */
}
}
@@ -362,7 +362,7 @@
* with a chosen value; the widget shipped that way on every dark router. */
.cbi-dropdown[open] > ul.dropdown > li[selected] {
/* @mirror selected-row/paint */
background: var(--fs-panel2); color: var(--fs-accent); box-shadow: inset 2px 0 0 var(--fs-accent);
background: var(--fs-panel2); color: var(--fs-accent); box-shadow: var(--fs-row-rail);
/* @endmirror */
}
/* THE FOCUSED ROW WEARS THE THEME'S RING, not the browser's.
@@ -375,8 +375,9 @@
* browser shipped.
*
* Inset rather than the token's outer 3px: the menu clips its own overflow, so an outer ring is
* cut on the first and last rows. The rail is restated INSIDE this ring so a focused row that is
* also selected keeps its rail a shorthand cannot merge with the one below.
* cut on the first and last rows. The rail rides INSIDE this ring so a focused row that is also
* selected keeps it a shorthand cannot merge with the one below, so the two are listed
* together, and `--fs-row-rail` is why that is a reference rather than a third copy.
*
* Both selectors carry `[selected]`-level weight on purpose: the selected-row paint below is
* (0,4,1) and a bare `li:focus-visible` ties with it, which would leave the focus ring to source
@@ -384,7 +385,7 @@
.cbi-dropdown[open] > ul.dropdown > li[selected]:focus-visible,
.cbi-dropdown[open] > ul.dropdown > li:focus-visible {
outline: none;
box-shadow: inset 2px 0 0 var(--fs-accent), inset 0 0 0 2px var(--fs-accent-soft);
box-shadow: var(--fs-row-rail), inset 0 0 0 2px var(--fs-accent-soft);
}
/* A MULTI-select row says "chosen" with its CHECKBOX, so it does not also need the rail.
+2 -2
View File
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=natflow
PKG_VERSION:=20260531
PKG_RELEASE:=63
PKG_RELEASE:=64
PKG_SOURCE:=$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
PKG_SOURCE_PROTO:=git
PKG_SOURCE_VERSION:=a2e7cbafc787ba9a0e86550bd3f4d03207fb9678
PKG_SOURCE_VERSION:=b3e749ae53becc4d31596e40d18e4875e518adc1
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
PKG_LICENSE:=GPL-2.0