mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-10 18:34:18 +08:00
🤞 Sync 2026-08-10 09:21:08
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gecoosac
|
||||
PKG_VERSION:=2.2.20251015
|
||||
PKG_RELEASE:=19
|
||||
PKG_RELEASE:=20
|
||||
|
||||
PKG_MAINTAINER:=Roc Lai <laipeng668@qq.com>
|
||||
PKG_LICENSE:=AGPL-3.0-only
|
||||
|
||||
@@ -86,50 +86,62 @@ normalize_path() {
|
||||
printf '%s\n' "$normalized"
|
||||
}
|
||||
|
||||
path_has_clear_stage_component() {
|
||||
local path="$1" part rest
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
rest="${path#/}"
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
case "$part" in
|
||||
.gecoosac-clear.*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_uses_clear_stage() {
|
||||
local path="$1" resolved
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
case "$path" in
|
||||
/.gecoosac-clear.*|/.gecoosac-clear.*/*|*/.gecoosac-clear.*|*/.gecoosac-clear.*/*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -e "$1" ] || [ -L "$1" ]; then
|
||||
resolved="$(readlink -f "$1" 2>/dev/null)" || return 1
|
||||
resolved="$(normalize_path "$resolved")" || return 1
|
||||
case "$resolved" in
|
||||
/.gecoosac-clear.*|/.gecoosac-clear.*/*|*/.gecoosac-clear.*|*/.gecoosac-clear.*/*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
path_has_clear_stage_component "$path" && return 0
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
||||
path_has_clear_stage_component "$resolved" && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_supported_upload_path() {
|
||||
local path="$1" storage
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
path_uses_clear_stage "$path" && return 1
|
||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
||||
|
||||
case "$path" in
|
||||
/mnt/*/gecoosac/upload)
|
||||
storage="${path#/mnt/}"
|
||||
storage="${storage%/gecoosac/upload}"
|
||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_safe_upload_dir() {
|
||||
local path physical
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
physical="$(managed_dir_path upload "$path")" || return 1
|
||||
case "$path" in
|
||||
/etc/gecoosac|/etc/gecoosac/*) return 1 ;;
|
||||
esac
|
||||
case "$physical" in
|
||||
/etc/gecoosac|/etc/gecoosac/*) return 1 ;;
|
||||
esac
|
||||
|
||||
case "$path" in
|
||||
*/gecoosac/upload) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
case "$physical" in
|
||||
*/gecoosac/upload) return 0 ;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
is_supported_upload_path "$path" || return 1
|
||||
is_supported_upload_path "$physical"
|
||||
}
|
||||
|
||||
is_path_in_dir() {
|
||||
@@ -614,18 +626,35 @@ managed_dir_path() {
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_upload_storage_root() {
|
||||
local path="$1" root
|
||||
|
||||
is_supported_upload_path "$path" || return 1
|
||||
case "$path" in
|
||||
"$DEFAULT_UPLOAD_DIR") root=/tmp ;;
|
||||
/mnt/*/gecoosac/upload) root="${path%/gecoosac/upload}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
|
||||
[ -d "$root" ] && [ ! -L "$root" ] || return 1
|
||||
is_secure_upload_dir "$root"
|
||||
}
|
||||
|
||||
ensure_managed_dir() {
|
||||
local role="$1" path physical
|
||||
|
||||
path="$(normalize_path "$2")" || return 1
|
||||
physical="$(managed_dir_path "$role" "$path")" || return 1
|
||||
case "$role" in
|
||||
upload) is_safe_upload_dir "$path" || return 1 ;;
|
||||
upload)
|
||||
is_safe_upload_dir "$path" || return 1
|
||||
ensure_upload_storage_root "$physical" || return 1
|
||||
;;
|
||||
db) is_safe_db_dir "$path" "$upload_dir" || return 1 ;;
|
||||
pid) is_safe_pid_dir "$path" "$upload_dir" || return 1 ;;
|
||||
tls) [ "$path" = "/etc/gecoosac/tls" ] || return 1 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
physical="$(managed_dir_path "$role" "$path")" || return 1
|
||||
ensure_secure_dir_tree "$physical" || return 1
|
||||
[ -d "$path" ]
|
||||
}
|
||||
|
||||
@@ -90,27 +90,62 @@ managed_dir_path() {
|
||||
esac
|
||||
}
|
||||
|
||||
path_has_clear_stage_component() {
|
||||
local path="$1" part rest
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
rest="${path#/}"
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
case "$part" in
|
||||
.gecoosac-clear.*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_uses_clear_stage() {
|
||||
local path="$1" resolved
|
||||
|
||||
path_has_clear_stage_component "$path" && return 0
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
||||
path_has_clear_stage_component "$resolved" && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_supported_upload_path() {
|
||||
local path="$1" storage
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
path_uses_clear_stage "$path" && return 1
|
||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
||||
|
||||
case "$path" in
|
||||
/mnt/*/gecoosac/upload)
|
||||
storage="${path#/mnt/}"
|
||||
storage="${storage%/gecoosac/upload}"
|
||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_safe_upload_dir() {
|
||||
local path physical
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
physical="$(managed_dir_path upload "$path")" || return 1
|
||||
case "$path" in
|
||||
/etc/gecoosac|/etc/gecoosac/*) return 1 ;;
|
||||
esac
|
||||
case "$physical" in
|
||||
/etc/gecoosac|/etc/gecoosac/*) return 1 ;;
|
||||
esac
|
||||
|
||||
case "$path" in
|
||||
*/gecoosac/upload) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
case "$physical" in
|
||||
*/gecoosac/upload) return 0 ;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
is_supported_upload_path "$path" || return 1
|
||||
is_supported_upload_path "$physical"
|
||||
}
|
||||
|
||||
is_path_in_dir() {
|
||||
@@ -247,7 +282,7 @@ cleanup_legacy_upload_dir() {
|
||||
|
||||
normalize_dir_option() {
|
||||
local option="$1"
|
||||
local validator="$3"
|
||||
local validator="$2"
|
||||
local value normalized upload_dir
|
||||
|
||||
value="$(uci -q get "gecoosac.config.${option}")"
|
||||
@@ -320,8 +355,8 @@ migrate_config() {
|
||||
ensure_section || exit 1
|
||||
migrate_config || exit 1
|
||||
normalize_upload_dir || exit 1
|
||||
normalize_dir_option db_dir "$DEFAULT_DB_DIR" is_safe_db_dir || exit 1
|
||||
normalize_dir_option piddir "$DEFAULT_PID_DIR" is_safe_pid_dir || exit 1
|
||||
normalize_dir_option db_dir is_safe_db_dir || exit 1
|
||||
normalize_dir_option piddir is_safe_pid_dir || exit 1
|
||||
set_default enabled 0 || exit 1
|
||||
set_default port 60650 || exit 1
|
||||
set_default isonlyoneprot 1 || exit 1
|
||||
|
||||
+11
-2
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_ghttpd:=$(ARCH)
|
||||
|
||||
PKG_NAME:=ghttpd
|
||||
PKG_VERSION:=0.0.2
|
||||
PKG_RELEASE:=4
|
||||
PKG_VERSION:=0.0.3
|
||||
PKG_RELEASE:=5
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/Carseason/openwrt-packages/releases/download/prebuilt/
|
||||
PKG_HASH:=skip
|
||||
@@ -49,6 +49,15 @@ if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
fi
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/postrm
|
||||
#!/bin/sh
|
||||
for service in uhttpd nginx; do
|
||||
[ -x "/etc/init.d/$$service" ] || continue
|
||||
"/etc/init.d/$$service" enabled && "/etc/init.d/$$service" restart
|
||||
done
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ stop_default_httpd_services() {
|
||||
start_default_httpd_services() {
|
||||
for service in uhttpd nginx; do
|
||||
[ -x "/etc/init.d/$service" ] || continue
|
||||
"/etc/init.d/$service" enabled && "/etc/init.d/$service" start
|
||||
"/etc/init.d/$service" enabled && "/etc/init.d/$service" restart
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-gecoosac
|
||||
PKG_VERSION:=2.2
|
||||
PKG_RELEASE:=19
|
||||
PKG_RELEASE:=20
|
||||
|
||||
LUCI_TITLE:=LuCI Support for gecoosac
|
||||
LUCI_DEPENDS:=+luci-base +gecoosac +gecoosac-files
|
||||
|
||||
@@ -11,7 +11,6 @@ const DEFAULT_DB_DIR = '/etc/gecoosac';
|
||||
const DEFAULT_CRT_FILE = '/etc/gecoosac/tls/gecoosac.crt';
|
||||
const DEFAULT_KEY_FILE = '/etc/gecoosac/tls/gecoosac.key';
|
||||
const DEFAULT_PID_DIR = '/var/run';
|
||||
const CONFIG_BACKUP_DIR = '/etc/gecoosac';
|
||||
const DB_DIR_PREFIXES = [ '/etc/gecoosac', '/tmp/gecoosac', '/var/lib/gecoosac' ];
|
||||
const PID_DIR_PREFIXES = [ '/var/run', '/tmp/gecoosac' ];
|
||||
const CLEAR_STAGE_PATH_ERROR = _('Paths under .gecoosac-clear.* are reserved for upload cleanup.');
|
||||
@@ -181,13 +180,26 @@ function usesManagedPath(value) {
|
||||
(path !== null && (path.indexOf('/var/') === 0 || path.indexOf('/var/run/') === 0));
|
||||
}
|
||||
|
||||
function uploadStorageRoot(path) {
|
||||
if (path === DEFAULT_UPLOAD_DIR)
|
||||
return '/tmp';
|
||||
|
||||
const segments = path === null ? [] : path.split('/');
|
||||
|
||||
if (segments.length === 5 && segments[1] === 'mnt' && segments[2] &&
|
||||
segments[3] === 'gecoosac' && segments[4] === 'upload')
|
||||
return '/mnt/' + segments[2];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function validUploadDir(value, policy) {
|
||||
const path = normalizePath(value);
|
||||
const physical = managedPath(value, policy);
|
||||
const storageRoot = uploadStorageRoot(path);
|
||||
const physicalStorageRoot = uploadStorageRoot(physical);
|
||||
|
||||
return path !== null && physical !== null && path.endsWith('/gecoosac/upload') &&
|
||||
physical.endsWith('/gecoosac/upload') && !pathInDir(path, CONFIG_BACKUP_DIR) &&
|
||||
!pathInDir(physical, CONFIG_BACKUP_DIR);
|
||||
return !usesClearStagePath(value) && storageRoot !== null && storageRoot === physicalStorageRoot;
|
||||
}
|
||||
|
||||
function validPathPrefix(value, prefixes) {
|
||||
@@ -451,7 +463,7 @@ return view.extend({
|
||||
httpsOption.onchange = revalidateProtocolOptions;
|
||||
|
||||
o = s.option(form.Value, 'upload_dir', _('Upload dir path'),
|
||||
_('Upload AP upgrade firmware here. Use an absolute path ending with /gecoosac/upload, for example /tmp/gecoosac/upload.<br />Do not place it under /etc/gecoosac because that directory is backed up during sysupgrade.'));
|
||||
_('Upload AP upgrade firmware here. Use /tmp/gecoosac/upload or /mnt/storage-name/gecoosac/upload. The /mnt/storage-name directory must already exist and be root-owned and private.'));
|
||||
uploadDirOption = o;
|
||||
o.placeholder = DEFAULT_UPLOAD_DIR;
|
||||
o.default = DEFAULT_UPLOAD_DIR;
|
||||
@@ -465,7 +477,7 @@ return view.extend({
|
||||
|
||||
return validUploadDir(value, pathPolicy)
|
||||
? true
|
||||
: _('Upload directory must be an absolute path ending with /gecoosac/upload and must not be under /etc/gecoosac.');
|
||||
: _('Upload directory must be /tmp/gecoosac/upload or /mnt/storage-name/gecoosac/upload.');
|
||||
};
|
||||
|
||||
o = s.option(form.Value, 'db_dir', _('Database dir path'),
|
||||
|
||||
@@ -55,17 +55,15 @@ msgid "Upload dir path"
|
||||
msgstr "上传目录"
|
||||
|
||||
msgid ""
|
||||
"Upload AP upgrade firmware here. Use an absolute path ending with "
|
||||
"/gecoosac/upload, for example /tmp/gecoosac/upload.<br />Do not place it "
|
||||
"under /etc/gecoosac because that directory is backed up "
|
||||
"during sysupgrade."
|
||||
"Upload AP upgrade firmware here. Use /tmp/gecoosac/upload or /mnt/storage-"
|
||||
"name/gecoosac/upload. The /mnt/storage-name directory must already exist "
|
||||
"and be root-owned and private."
|
||||
msgstr ""
|
||||
"AP 升级固件会上传到此目录。请使用以 /gecoosac/upload 结尾的绝对路径,例如 "
|
||||
"/tmp/gecoosac/upload。<br />不要放在 /etc/gecoosac 下,因为该目录会在 "
|
||||
"sysupgrade 时被备份。"
|
||||
"AP 升级固件会上传到此目录。请使用 /tmp/gecoosac/upload 或 /mnt/storage-"
|
||||
"name/gecoosac/upload。/mnt/storage-name 目录必须已存在、归 root 所有且权限安全。"
|
||||
|
||||
msgid "Upload directory must be an absolute path ending with /gecoosac/upload and must not be under /etc/gecoosac."
|
||||
msgstr "上传目录必须是以 /gecoosac/upload 结尾的绝对路径,且不能位于 /etc/gecoosac 下。"
|
||||
msgid "Upload directory must be /tmp/gecoosac/upload or /mnt/storage-name/gecoosac/upload."
|
||||
msgstr "上传目录必须是 /tmp/gecoosac/upload 或 /mnt/storage-name/gecoosac/upload。"
|
||||
|
||||
msgid "Database dir path"
|
||||
msgstr "数据库目录"
|
||||
|
||||
@@ -55,17 +55,15 @@ msgid "Upload dir path"
|
||||
msgstr "上傳目錄"
|
||||
|
||||
msgid ""
|
||||
"Upload AP upgrade firmware here. Use an absolute path ending with "
|
||||
"/gecoosac/upload, for example /tmp/gecoosac/upload.<br />Do not place it "
|
||||
"under /etc/gecoosac because that directory is backed up "
|
||||
"during sysupgrade."
|
||||
"Upload AP upgrade firmware here. Use /tmp/gecoosac/upload or /mnt/storage-"
|
||||
"name/gecoosac/upload. The /mnt/storage-name directory must already exist "
|
||||
"and be root-owned and private."
|
||||
msgstr ""
|
||||
"AP 升級韌體會上傳到此目錄。請使用以 /gecoosac/upload 結尾的絕對路徑,例如 "
|
||||
"/tmp/gecoosac/upload。<br />不要放在 /etc/gecoosac 下,因為該目錄會在 "
|
||||
"sysupgrade 時被備份。"
|
||||
"AP 升級韌體會上傳到此目錄。請使用 /tmp/gecoosac/upload 或 /mnt/storage-"
|
||||
"name/gecoosac/upload。/mnt/storage-name 目錄必須已存在、由 root 擁有且權限安全。"
|
||||
|
||||
msgid "Upload directory must be an absolute path ending with /gecoosac/upload and must not be under /etc/gecoosac."
|
||||
msgstr "上傳目錄必須是以 /gecoosac/upload 結尾的絕對路徑,且不能位於 /etc/gecoosac 下。"
|
||||
msgid "Upload directory must be /tmp/gecoosac/upload or /mnt/storage-name/gecoosac/upload."
|
||||
msgstr "上傳目錄必須是 /tmp/gecoosac/upload 或 /mnt/storage-name/gecoosac/upload。"
|
||||
|
||||
msgid "Database dir path"
|
||||
msgstr "資料庫目錄"
|
||||
|
||||
@@ -58,6 +58,55 @@ normalize_path() {
|
||||
printf '%s\n' "$normalized"
|
||||
}
|
||||
|
||||
path_has_clear_stage_component() {
|
||||
local path="$1" part rest
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
rest="${path#/}"
|
||||
while [ -n "$rest" ]; do
|
||||
part="${rest%%/*}"
|
||||
if [ "$part" = "$rest" ]; then
|
||||
rest=""
|
||||
else
|
||||
rest="${rest#*/}"
|
||||
fi
|
||||
case "$part" in
|
||||
.gecoosac-clear.*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_uses_clear_stage() {
|
||||
local path="$1" resolved
|
||||
|
||||
path_has_clear_stage_component "$path" && return 0
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
resolved="$(readlink -f "$path" 2>/dev/null)" || return 1
|
||||
path_has_clear_stage_component "$resolved" && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_supported_upload_path() {
|
||||
local path="$1" storage
|
||||
|
||||
path="$(normalize_path "$path")" || return 1
|
||||
path_uses_clear_stage "$path" && return 1
|
||||
[ "$path" = "$DEFAULT_UPLOAD_DIR" ] && return 0
|
||||
|
||||
case "$path" in
|
||||
/mnt/*/gecoosac/upload)
|
||||
storage="${path#/mnt/}"
|
||||
storage="${storage%/gecoosac/upload}"
|
||||
[ -n "$storage" ] && [ "${storage#*/}" = "$storage" ]
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
managed_dir_path() {
|
||||
local role="$1" path anchor
|
||||
|
||||
@@ -106,16 +155,7 @@ safe_upload_path() {
|
||||
local path
|
||||
|
||||
path="$(normalize_path "$1")" || return 1
|
||||
case "$path" in
|
||||
/etc/gecoosac|/etc/gecoosac/*) return 1 ;;
|
||||
esac
|
||||
[ "$path" = "/tmp/gecoosac/upload" ] && return 0
|
||||
|
||||
case "$path" in
|
||||
*/gecoosac/upload) return 0 ;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
is_supported_upload_path "$path"
|
||||
}
|
||||
|
||||
path_in_dir() {
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.8.1
|
||||
PKG_RELEASE:=210
|
||||
PKG_RELEASE:=211
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -103,30 +103,7 @@ config nodes 'myshunt'
|
||||
config shunt_rules 'DirectGame'
|
||||
option remarks 'DirectGame'
|
||||
option network 'tcp,udp'
|
||||
option domain_list '# steam直连域名获取国内CDN走国内线路下载
|
||||
cm.steampowered.com
|
||||
steamserver.net
|
||||
|
||||
# steam国内CDN华为云
|
||||
steampipe.steamcontent.tnkjmec.com
|
||||
# steam国内CDN白山云
|
||||
st.dl.eccdnx.com
|
||||
st.dl.bscstorage.net
|
||||
st.dl.pinyuncloud.com
|
||||
# steam国内CDN新流云(原金山云)(支持ipv6)
|
||||
dl.steam.clngaa.com
|
||||
# steam国内CDN网宿
|
||||
cdn.mileweb.cs.steampowered.com.8686c.com
|
||||
cdn-ws.content.steamchina.com
|
||||
# steam国内CDN腾讯云 (蒸汽中国独占)
|
||||
cdn-qc.content.steamchina.com
|
||||
# steam国内CDN阿里云(支持ipv6)
|
||||
cdn-ali.content.steamchina.com
|
||||
xz.pphimalayanrt.com
|
||||
lv.queniujq.cn
|
||||
alibaba.cdn.steampipe.steamcontent.com
|
||||
|
||||
# 国内游戏geosite域名
|
||||
option domain_list '# 国内游戏geosite域名
|
||||
geosite:category-games@cn'
|
||||
option ip_list '# steam直连IP
|
||||
45.121.184.0/24
|
||||
@@ -196,6 +173,9 @@ steamcloud-sydney.storage.googleapis.com
|
||||
steamcloud-taiwan.storage.googleapis.com
|
||||
steamcloud-eu.storage.googleapis.com
|
||||
domain:blizzard.com
|
||||
#Steam支付
|
||||
domain:checkout.steampowered.com
|
||||
domain:globalapi.smart2pay.com
|
||||
geosite:category-games'
|
||||
|
||||
config shunt_rules 'AIGC'
|
||||
|
||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
||||
FOOTSTRAP_VERSION?=
|
||||
ifneq ($(FOOTSTRAP_VERSION),)
|
||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||
PKG_RELEASE:=8
|
||||
PKG_RELEASE:=9
|
||||
endif
|
||||
|
||||
LUCI_TITLE:=Footstrap Theme
|
||||
|
||||
@@ -281,9 +281,36 @@
|
||||
white-space: normal;
|
||||
/* overflow-wrap dropped — base/40-tables.css breaks every cell; the copy that stood here
|
||||
* moved nothing against 267 matching cells. */
|
||||
padding-inline-end: min(200px, 50%);
|
||||
padding-inline-end: min(230px, 50%);
|
||||
font-size: var(--fs-type); font-weight: var(--fs-weight); color: var(--fs-text);
|
||||
}
|
||||
|
||||
/* ON A NARROW CARD THE VALUE TAKES ITS OWN LINE — a reserve cannot be made to fit there.
|
||||
*
|
||||
* The value is out of flow (the bar's ::after, pinned over its far edge), so the only thing
|
||||
* keeping it off the label is that padding, and the two are sized by different things: the
|
||||
* padding is a fraction of the CARD, the value is however wide LuCI's formatter made it.
|
||||
* `16.66 GiB / 16.66 GiB (100%)` measures 218px in the mono face at --fs-type-xs, and 50% of a
|
||||
* 380px phone card is 190 — so the figure printed straight through the end of the label:
|
||||
* "Дисковое пространство330.54 MiB / 7.98 GiB (4%)", reported from a phone. Measured across the
|
||||
* range: the reserve is at its 230px cap and clear of every value from a ~460px card up, and
|
||||
* short of the longest ones below that.
|
||||
*
|
||||
* Below the threshold the label keeps the whole line and the bar drops by one --fs-type-xs
|
||||
* line, which is the band the value then sits in — right-aligned, on its own line, above its
|
||||
* own bar. It costs one line per row on a phone and cannot collide at any label length.
|
||||
* `fs-view` rather than a container of its own: the Memory/Storage cards are already single
|
||||
* column at 800px (the grid collapse above), so below 560px the card IS the view minus its
|
||||
* gutters, and adding `container-type` to the card would size-contain a box the port tiles
|
||||
* deliberately keep uncontained. */
|
||||
@container fs-view (max-width: 560px) {
|
||||
body[data-page="admin-status-overview"] .cbi-section .table:not(:has(.tr.table-titles)) .tr:has(.cbi-progressbar) .td:first-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
body[data-page="admin-status-overview"] .cbi-section .table:not(:has(.tr.table-titles)) .tr:has(.cbi-progressbar) .cbi-progressbar {
|
||||
margin-block-start: calc(var(--fs-type-xs) * var(--fs-leading));
|
||||
}
|
||||
}
|
||||
/* bar cell — spans full width on row 2 */
|
||||
body[data-page="admin-status-overview"] .cbi-section .table:not(:has(.tr.table-titles)) .tr:has(.cbi-progressbar) .td:nth-child(2) {
|
||||
grid-column: 1 / -1; grid-row: 2;
|
||||
|
||||
@@ -1,4 +1,65 @@
|
||||
@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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Keyed on the controls' OWN ids rather than on the page: `data-page` differs between the system
|
||||
* and the kernel log, and a third-party LogreadBox has neither, while ui.js gives these two rows
|
||||
* the same ids wherever the widget is rendered. 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. */
|
||||
div:has(> #logFacilitySelect),
|
||||
div:has(> #logTextFilter) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
row-gap: var(--fs-space-2);
|
||||
}
|
||||
div:has(> #logFacilitySelect) > label,
|
||||
div:has(> #logTextFilter) > label { white-space: nowrap; }
|
||||
#logFacilitySelect, #logSeveritySelect, #logTextFilter, #logMaxRows {
|
||||
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. */
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
/* the two numeric-ish fields keep a sane ceiling: a max-rows box as wide as the filter reads as
|
||||
* a second search field. */
|
||||
#logMaxRows { 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.
|
||||
*
|
||||
* The SECOND row is deliberately left to the wrapping above. It holds two filters of different
|
||||
* shapes (a checkbox filter and a bare `Макс. строк:` pair), so the same four tracks would be
|
||||
* 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. Wrapping puts that row's two
|
||||
* filters on two lines by itself, which is all the grid was for. */
|
||||
@container fs-view (max-width: 560px) {
|
||||
div:has(> #logFacilitySelect) {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto minmax(0, 1fr);
|
||||
column-gap: var(--fs-space-1);
|
||||
}
|
||||
}
|
||||
|
||||
/* System Log / Kernel Log (luci-mod-status): the whole log is a readonly
|
||||
* <textarea id="syslog"> in a bare div, NOT in a .cbi-value-field. Stock bootstrap pairs
|
||||
* its generic `input, textarea { width: 210px }` with `#syslog { width: 100% }` and wins
|
||||
|
||||
@@ -26,6 +26,33 @@
|
||||
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 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 {
|
||||
inset-inline: calc(100% + var(--fs-space-2)) auto; bottom: auto; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* 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; }
|
||||
|
||||
@@ -249,6 +249,23 @@
|
||||
* the thing the scale exists to stop. Absorbed with the machinery above. */
|
||||
min-height: var(--fs-space-5);
|
||||
}
|
||||
/* …and the row's CONTENTS, which is where that font was being lost. A MULTI-select wraps every
|
||||
* item in `<form><label><input type=checkbox> text</label></form>` (ui.js builds the checkbox
|
||||
* that way), and `.cbi-value-field *` in theme/60-inputs.css hands mono to every descendant of a
|
||||
* field — it matches that form and that label DIRECTLY, while the rule above only reaches the
|
||||
* `<li>`, whose font the label then overrides rather than inherits. So the same widget drew two
|
||||
* fonts depending on one option: a single-select list, whose text sits in the `<li>` itself,
|
||||
* came out sans; every multi-select list came out mono, in the open menu AND in the closed
|
||||
* control that lists what is selected. Measured on the router with ui.Dropdown: `<li>` Manrope,
|
||||
* its `<form>`/`<label>` JetBrains Mono.
|
||||
*
|
||||
* It is a LABEL, not a value — the same distinction `<strong>` gets in theme/45-misc.css — and
|
||||
* mono spacing is what makes a wordy option list look stretched (`3G Band 1`, `LTE B1`, reported
|
||||
* against luci-app-modeminfo's Bands page). `inherit`, not the token again: the row above is the
|
||||
* one place the menu's font is chosen. The filter/create `<input>` inside the list is left
|
||||
* alone: what the user TYPES there is a value. */
|
||||
.cbi-dropdown > ul > li > form,
|
||||
.cbi-dropdown > ul > li label { font-family: inherit; }
|
||||
.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[display]:not([display="0"]) { border-inline-start: 0; }
|
||||
.cbi-dropdown > ul > li[placeholder] { color: var(--fs-faint); }
|
||||
/* the multiselect's inline filter/create inputs — moved from base with the generic `input`
|
||||
@@ -348,6 +365,47 @@
|
||||
background: var(--fs-panel2); color: var(--fs-accent); box-shadow: inset 2px 0 0 var(--fs-accent);
|
||||
/* @endmirror */
|
||||
}
|
||||
/* THE FOCUSED ROW WEARS THE THEME'S RING, not the browser's.
|
||||
*
|
||||
* ui.js focuses a row as it opens the menu (the first selected one, or the first row), and the
|
||||
* UA answers a programmatic focus with `outline: auto` — Chrome's 1px system-blue ring, drawn
|
||||
* OUTSIDE the row and following its 8px radius, so opening a dropdown that already has a value
|
||||
* flashed a loud blue capsule around the top row on every single-select and multi-select on the
|
||||
* router. Every other control in this theme wears --fs-focus-ring; this row wore whatever the
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* order. `docs/conventions.md`: win on specificity, never on order. */
|
||||
.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);
|
||||
}
|
||||
|
||||
/* A MULTI-select row says "chosen" with its CHECKBOX, so it does not also need the rail.
|
||||
*
|
||||
* The rail is `inset 2px 0 0` on a row with an 8px radius, so it is drawn as an ARC, not a bar —
|
||||
* fine as the single-select cue it was designed for, one row at a time, and wrong down a column
|
||||
* of them: three chosen bands rendered three blue crescents stacked against the menu's edge and
|
||||
* read as a broken frame (reported against luci-app-modeminfo's Bands page). The checkbox is a
|
||||
* non-colour cue in its own right, which is what WCAG 2.2 SC 1.4.1 asks of "selected" — a
|
||||
* single-select row has none, and keeps the rail.
|
||||
*
|
||||
* Written as an override rather than by narrowing the paint below, because those three
|
||||
* declarations are pinned byte-identical to `select option:checked` in theme/60-inputs.css
|
||||
* (tools/mirror.mjs) — a native `<select multiple>` option has no checkbox, so the rail is still
|
||||
* the only cue there and the two must not diverge. `:not(:focus-visible)` keeps this off the
|
||||
* focused row, so the ring above owns that state alone instead of tying with it. */
|
||||
.cbi-dropdown[multiple][open] > ul.dropdown > li[selected]:not(:focus-visible) { box-shadow: none; }
|
||||
.cbi-dropdown[multiple][open] > ul.dropdown > li[selected]:focus-visible {
|
||||
box-shadow: inset 0 0 0 2px var(--fs-accent-soft);
|
||||
}
|
||||
|
||||
.cbi-dropdown[open] > ul.dropdown > li.create-item { border-top: 1px solid var(--fs-border); margin-top: var(--fs-space-1); }
|
||||
/* The dropdown's "add item" field is a text input and takes the same radius as every other
|
||||
* one (theme/60-inputs.css). It said 8px here, and won or lost on source order. */
|
||||
|
||||
Reference in New Issue
Block a user