mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-05 18:44:06 +08:00
1 parent
b1cd3aafd3
commit
3cdb943a21
25 files changed
+614
-77
No files matched your search
+6
-6
@@ -5,7 +5,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=honk
|
||||
PKG_VERSION:=2026.11.03
|
||||
PKG_VERSION:=2026.11.04
|
||||
PKG_RELEASE:=1
|
||||
|
||||
# Pinned honk-core revision (upstream daeuniverse/honk main), cross-compiled by
|
||||
@@ -14,10 +14,10 @@ PKG_RELEASE:=1
|
||||
# assets behind CORE_COMMIT, so a plain feed build downloads and verifies them
|
||||
# without any generated file. A staged run may still override them through
|
||||
# honk/generated-stage.mk, which is generated and never committed.
|
||||
CORE_COMMIT:=77176ff8e8cc2b75385a7030dc376536c382574a
|
||||
CORE_COMMIT:=88d470c662e0ee7a3de3e67afc6129f67e66c1bf
|
||||
|
||||
# Immutable source asset tag holding this pin's core archives.
|
||||
HONK_SOURCE_TAG?=source-41594075839
|
||||
HONK_SOURCE_TAG?=source-41792584995
|
||||
HONK_SOURCE_URL?=https://github.com/kenzok8/openwrt-honk/releases/download/$(HONK_SOURCE_TAG)
|
||||
|
||||
-include $(CURDIR)/generated-stage.mk
|
||||
@@ -25,9 +25,9 @@ HONK_SOURCE_URL?=https://github.com/kenzok8/openwrt-honk/releases/download/$(HON
|
||||
HONK_ASSET_X86_64?=honk-core-$(CORE_COMMIT)-x86_64.tar.gz
|
||||
HONK_ASSET_AARCH64?=honk-core-$(CORE_COMMIT)-aarch64.tar.gz
|
||||
HONK_ASSET_ARMV7?=honk-core-$(CORE_COMMIT)-armv7.tar.gz
|
||||
HONK_HASH_X86_64?=ad4801757f8b47505aa99cbdc47e4d606b8afe1a6269547d8073f68c20904a0e
|
||||
HONK_HASH_AARCH64?=91a9a9929ec32d24de3c105f2a51d83a4975913a68e1446e57c2b1faf63adce6
|
||||
HONK_HASH_ARMV7?=a5b288c677698a44cc45084baf0bb67b1fb2b9ca624744a08d7a469093fc9870
|
||||
HONK_HASH_X86_64?=5a389e97296f6b5d3aad5a7180a57f9669845f6baa9b52742295de23147ac07e
|
||||
HONK_HASH_AARCH64?=7261ed6b98b13fa3f7246175b5d9222feecdb3cdf3946b7c0bc296641866b345
|
||||
HONK_HASH_ARMV7?=1333e0598a493183af58e516173c66b44e692223949247cd32e53934dcebfeb4
|
||||
|
||||
# ARCH_PACKAGES is the package arch (e.g. arm_cortex-a7_neon-vfpv4);
|
||||
# ARCH is the CPU arch (x86_64/aarch64/arm) and coincides for 64-bit.
|
||||
|
||||
+246
-14
@@ -376,6 +376,21 @@ cleanup_initialize() {
|
||||
fi
|
||||
}
|
||||
|
||||
# 交还面板管理时也用它,保证两条路径生成的 system.dae 一致。
|
||||
render_runtime_system() {
|
||||
printf "global {\n wan_interface: auto\n lan_interface: '%s'\n" "$1"
|
||||
printf " log_level: info\n dial_mode: domain\n allow_insecure: false\n"
|
||||
printf " auto_config_kernel_parameter: true\n data_dir: '/etc/honk'\n"
|
||||
printf " store_subscribe: true\n nfqueue_enable: true\n}\n\n"
|
||||
printf "experimental {\n native_api {\n enabled: true\n listen: '%s:%s'\n" "$2" "$3"
|
||||
printf " password_auth: true\n allow_anonymous_loopback: false\n"
|
||||
printf " config_write: true\n ui: '/usr/share/doona'\n }\n"
|
||||
if [ "$clash_api_enabled" = 1 ]; then
|
||||
printf " clash_api {\n external_controller: '%s:%s'\n external_ui: '/usr/share/honk/zashboard'\n secret: '%s'\n default_mode: Rule\n }\n" "$2" "$clash_api_port" "$clash_api_secret"
|
||||
fi
|
||||
printf "}\n"
|
||||
}
|
||||
|
||||
write_runtime_system() {
|
||||
lan_device=$1
|
||||
lan_ip=$2
|
||||
@@ -385,19 +400,7 @@ write_runtime_system() {
|
||||
current_hash=$(sha256sum /etc/honk/system.dae 2>/dev/null | awk '{print $1}')
|
||||
[ "$current_hash" = "$system_written_hash" ] || { fail "写入运行配置时 system.dae 被其他进程改动,已中止"; return 1; }
|
||||
temp_system=$(mktemp /etc/honk/.system.dae.XXXXXX) || return 1
|
||||
{
|
||||
printf "global {\n wan_interface: auto\n lan_interface: '%s'\n" "$lan_device"
|
||||
printf " log_level: info\n dial_mode: domain\n allow_insecure: false\n"
|
||||
printf " auto_config_kernel_parameter: true\n data_dir: '/etc/honk'\n"
|
||||
printf " store_subscribe: true\n nfqueue_enable: true\n}\n\n"
|
||||
printf "experimental {\n native_api {\n enabled: true\n listen: '%s:%s'\n" "$system_listen" "$system_port"
|
||||
printf " password_auth: true\n allow_anonymous_loopback: false\n"
|
||||
printf " config_write: true\n ui: '/usr/share/doona'\n }\n"
|
||||
if [ "$clash_api_enabled" = 1 ]; then
|
||||
printf " clash_api {\n external_controller: '%s:%s'\n external_ui: '/usr/share/honk/zashboard'\n secret: '%s'\n default_mode: Rule\n }\n" "$system_listen" "$clash_api_port" "$clash_api_secret"
|
||||
fi
|
||||
printf "}\n"
|
||||
} > "$temp_system" || { rm -f "$temp_system"; return 1; }
|
||||
render_runtime_system "$lan_device" "$system_listen" "$system_port" > "$temp_system" || { rm -f "$temp_system"; return 1; }
|
||||
chmod 0600 "$temp_system" || { rm -f "$temp_system"; return 1; }
|
||||
current_hash=$(sha256sum /etc/honk/system.dae 2>/dev/null | awk '{print $1}')
|
||||
[ "$current_hash" = "$system_written_hash" ] || { rm -f "$temp_system"; fail "写入运行配置时 system.dae 被其他进程改动,已中止"; return 1; }
|
||||
@@ -589,6 +592,232 @@ settings() {
|
||||
echo '{"ok":true,"message":"settings_saved"}'
|
||||
}
|
||||
|
||||
# 把 global/experimental 摊平成「路径=值」,取值和比对共用。
|
||||
system_entries() {
|
||||
# 逐字符扫描:大括号可以跟值写在同一行,按整行判断会把归属解析错。
|
||||
awk '
|
||||
function flush(stmt, key, val, p, i, fields) {
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", stmt)
|
||||
if (stmt == "" || d == 0) return
|
||||
if (path[1] != "global" && path[1] != "experimental") return
|
||||
if (stmt !~ /^[A-Za-z_][A-Za-z0-9_]*[[:space:]]*:/) return
|
||||
# 一行写多个字段无法可靠拆分,交给调用方拒绝,不能只取第一个就当成全部。
|
||||
fields = stmt
|
||||
gsub(/"[^"]*"/, "", fields)
|
||||
gsub(/\047[^\047]*\047/, "", fields)
|
||||
if (gsub(/[A-Za-z_][A-Za-z0-9_]*[[:space:]]*:/, ":", fields) > 1) {
|
||||
print "!unsupported"
|
||||
return
|
||||
}
|
||||
key = stmt
|
||||
sub(/[[:space:]]*:.*/, "", key)
|
||||
val = stmt
|
||||
sub(/^[^:]*:[[:space:]]*/, "", val)
|
||||
if (val ~ /^\047/) { val = substr(val, 2); sub(/\047.*/, "", val) }
|
||||
else if (val ~ /^"/) { val = substr(val, 2); sub(/".*/, "", val) }
|
||||
p = path[1]
|
||||
for (i = 2; i <= d; i++) p = p "." path[i]
|
||||
print p "." key "=" val
|
||||
}
|
||||
{
|
||||
line = $0
|
||||
sub(/\r$/, "", line)
|
||||
n = length(line)
|
||||
for (i = 1; i <= n; i++) {
|
||||
c = substr(line, i, 1)
|
||||
if (quote != "") {
|
||||
buf = buf c
|
||||
if (c == quote) quote = ""
|
||||
continue
|
||||
}
|
||||
if (c == "\047" || c == "\"") { quote = c; buf = buf c; continue }
|
||||
if (c == "#") break
|
||||
if (c == "{") {
|
||||
name = buf
|
||||
sub(/[[:space:]]*:.*/, "", name)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
|
||||
path[++d] = name
|
||||
buf = ""
|
||||
continue
|
||||
}
|
||||
if (c == "}") {
|
||||
flush(buf)
|
||||
buf = ""
|
||||
if (d > 0) d--
|
||||
continue
|
||||
}
|
||||
buf = buf c
|
||||
}
|
||||
flush(buf)
|
||||
buf = ""
|
||||
}
|
||||
' "$1"
|
||||
}
|
||||
|
||||
entry_value() {
|
||||
awk -v key="$1" 'index($0, key "=") == 1 { print substr($0, length(key) + 2); exit }' "$2"
|
||||
}
|
||||
|
||||
# 只删顶级 global/experimental,在第一块的位置补 include,其余内容原样保留。
|
||||
strip_system_blocks() {
|
||||
awk '
|
||||
skip {
|
||||
n = length($0)
|
||||
for (i = 1; i <= n; i++) {
|
||||
c = substr($0, i, 1)
|
||||
if (q != "") { if (c == q) q = ""; continue }
|
||||
if (c == "\047" || c == "\"") { q = c; continue }
|
||||
if (c == "#") break
|
||||
if (c == "{") depth++
|
||||
else if (c == "}") depth--
|
||||
}
|
||||
if (depth <= 0) skip = 0
|
||||
next
|
||||
}
|
||||
/^(global|experimental)[[:space:]]*\{/ {
|
||||
if (!added) { print "include {\n system.dae\n}"; added = 1 }
|
||||
depth = gsub(/\{/, "{")
|
||||
depth -= gsub(/\}/, "}")
|
||||
skip = depth > 0
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$1"
|
||||
}
|
||||
|
||||
device_network() {
|
||||
. /lib/functions/network.sh
|
||||
network_get_device dn_dev "$lan_network" && [ "$dn_dev" = "$1" ] && { echo "$lan_network"; return 0; }
|
||||
for dn_iface in $(ubus list 'network.interface.*' 2>/dev/null | sed 's/^network\.interface\.//'); do
|
||||
network_get_device dn_dev "$dn_iface" || continue
|
||||
[ "$dn_dev" = "$1" ] && { echo "$dn_iface"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
valid_port() {
|
||||
case "$1" in ''|*[!0-9]*) return 1 ;; esac
|
||||
[ "$1" -ge 1024 ] && [ "$1" -le 65535 ]
|
||||
}
|
||||
|
||||
detach_fail() {
|
||||
echo "{\"ok\":false,\"message\":\"$1\"}"
|
||||
return 1
|
||||
}
|
||||
|
||||
detach_rollback() {
|
||||
dr_ok=1
|
||||
honk_lifecycle_stop 30 >/dev/null 2>&1 || dr_ok=0
|
||||
cp -p "$detach_dir/old-system.dae" /etc/honk/.system.dae.rollback && mv -f /etc/honk/.system.dae.rollback /etc/honk/system.dae || dr_ok=0
|
||||
cp -p "$detach_dir/old-config.dae" /etc/honk/.config.dae.rollback && mv -f /etc/honk/.config.dae.rollback /etc/honk/config.dae || dr_ok=0
|
||||
{ uci import honk < "$detach_dir/old.uci" && uci commit honk && chmod 0600 /etc/config/honk; } || dr_ok=0
|
||||
if [ "$dr_ok" = 1 ] && [ "$old_running" = 1 ]; then honk_lifecycle_start 30 || dr_ok=0; fi
|
||||
[ "$dr_ok" = 1 ]
|
||||
}
|
||||
|
||||
# 把文件里的 global/experimental 交还 system.dae 管理;dry_run 只返回会变动的项。
|
||||
detach() {
|
||||
if [ -e /etc/.honk-update/journal ] || [ -L /etc/.honk-update/journal ]; then detach_fail update_recovery_required; return 1; fi
|
||||
. /usr/share/libubox/jshn.sh || { detach_fail jshn_unavailable; return 1; }
|
||||
detach_request=$(head -c 4096)
|
||||
dry_run=
|
||||
[ -z "$detach_request" ] || json_load "$detach_request" 2>/dev/null || { detach_fail invalid_request; return 1; }
|
||||
[ -z "$detach_request" ] || json_get_var dry_run dry_run
|
||||
case "$dry_run" in 1|true) dry_run=1 ;; *) dry_run=0 ;; esac
|
||||
load_settings
|
||||
[ "$initialized" = 1 ] || { detach_fail not_initialized; return 1; }
|
||||
[ "$settings_owner" = file ] || { detach_fail config_not_file_owned; return 1; }
|
||||
[ -f /etc/honk/system.dae ] && [ ! -L /etc/honk/system.dae ] || { detach_fail system_config_unavailable; return 1; }
|
||||
umask 077
|
||||
detach_dir=$(mktemp -d /etc/honk/.detach.XXXXXX) || { detach_fail config_write_failed; return 1; }
|
||||
trap 'rm -rf "$detach_dir"' 0
|
||||
old_hash=$(sha256sum /etc/honk/config.dae | awk '{print $1}')
|
||||
system_entries /etc/honk/config.dae > "$detach_dir/old.entries"
|
||||
grep -q '^!unsupported$' "$detach_dir/old.entries" && { detach_fail detach_layout_unsupported; return 1; }
|
||||
|
||||
# 能对应到面板的值照搬,对不上的沿用面板现值。
|
||||
file_device=$(entry_value global.lan_interface "$detach_dir/old.entries")
|
||||
if [ -n "$file_device" ] && detach_network=$(device_network "$file_device"); then lan_network=$detach_network; fi
|
||||
file_port=$(entry_value experimental.native_api.listen "$detach_dir/old.entries")
|
||||
file_port=${file_port##*:}
|
||||
valid_port "$file_port" && listen_port=$file_port
|
||||
old_clash_port=$clash_api_port
|
||||
old_clash_secret=$clash_api_secret
|
||||
clash_api_enabled=0
|
||||
if grep -q '^experimental\.clash_api\.' "$detach_dir/old.entries"; then
|
||||
clash_api_enabled=1
|
||||
file_clash=$(entry_value experimental.clash_api.external_controller "$detach_dir/old.entries")
|
||||
clash_api_port=${file_clash##*:}
|
||||
clash_api_secret=$(entry_value experimental.clash_api.secret "$detach_dir/old.entries")
|
||||
valid_clash_api || { clash_api_enabled=0; clash_api_port=$old_clash_port; clash_api_secret=$old_clash_secret; }
|
||||
fi
|
||||
check_network && network_settings || { detach_fail lan_network_unavailable; return 1; }
|
||||
|
||||
render_runtime_system "$lan_device" "$lan_ip" "$listen_port" > "$detach_dir/system.dae" || { detach_fail config_write_failed; return 1; }
|
||||
strip_system_blocks /etc/honk/config.dae > "$detach_dir/config.dae" || { detach_fail config_write_failed; return 1; }
|
||||
[ "$(config_owner "$detach_dir/config.dae")" = system ] || { detach_fail detach_layout_unsupported; return 1; }
|
||||
detach_check=$("$HONK_CORE" config validate --config "$detach_dir/config.dae" 2>/dev/null)
|
||||
[ "$(printf '%s' "$detach_check" | jsonfilter -e '@.ok' 2>/dev/null)" = true ] || { detach_fail detach_validation_failed; return 1; }
|
||||
|
||||
if [ "$dry_run" = 1 ]; then
|
||||
system_entries "$detach_dir/system.dae" > "$detach_dir/new.entries"
|
||||
json_init
|
||||
json_add_boolean ok 1
|
||||
json_add_string message detach_preview
|
||||
json_add_string lan_network "$lan_network"
|
||||
json_add_int listen_port "$listen_port"
|
||||
json_add_boolean clash_api_enabled "$clash_api_enabled"
|
||||
json_add_int clash_api_port "$clash_api_port"
|
||||
json_add_array changes
|
||||
awk '
|
||||
{ i = index($0, "="); k = substr($0, 1, i - 1); v = substr($0, i + 1) }
|
||||
NR == FNR { seen[k] = 1; val[k] = v; next }
|
||||
k ~ /\.secret$/ { if (!seen[k]) print k "\t***\t"; else if (val[k] != v) print k "\t***\t***"; next }
|
||||
!seen[k] { print k "\t" v "\t"; next }
|
||||
val[k] != v { print k "\t" v "\t" val[k] }
|
||||
' "$detach_dir/new.entries" "$detach_dir/old.entries" > "$detach_dir/changes"
|
||||
while IFS=' ' read -r change_key change_old change_new; do
|
||||
json_add_object ""
|
||||
json_add_string key "$change_key"
|
||||
json_add_string old "$change_old"
|
||||
json_add_string new "$change_new"
|
||||
json_close_object
|
||||
done < "$detach_dir/changes"
|
||||
json_close_array
|
||||
json_dump
|
||||
return 0
|
||||
fi
|
||||
|
||||
honk_lock_acquire || { detach_fail operation_in_progress; return 1; }
|
||||
trap 'honk_lock_release >/dev/null 2>&1; rm -rf "$detach_dir"' 0
|
||||
[ "$(sha256sum /etc/honk/config.dae | awk '{print $1}')" = "$old_hash" ] || { detach_fail config_changed_concurrently; return 1; }
|
||||
old_running=0
|
||||
if managed_core_pid >/dev/null 2>&1; then
|
||||
old_running=1
|
||||
elif pidof honk-core >/dev/null 2>&1; then
|
||||
detach_fail unmanaged_core_running
|
||||
return 1
|
||||
fi
|
||||
cp -p /etc/honk/config.dae "$detach_dir/old-config.dae" && cp -p /etc/honk/system.dae "$detach_dir/old-system.dae" && uci export honk > "$detach_dir/old.uci" || { detach_fail config_write_failed; return 1; }
|
||||
cp -p /etc/honk/config.dae /etc/honk/.config-before-detach.tmp && mv -f /etc/honk/.config-before-detach.tmp /etc/honk/config-before-detach.dae || { detach_fail config_write_failed; return 1; }
|
||||
if [ "$old_running" = 1 ]; then honk_lifecycle_stop 30 || { detach_fail service_stop_unconfirmed; return 1; }; fi
|
||||
chmod 0600 "$detach_dir/system.dae" "$detach_dir/config.dae"
|
||||
if ! { cp -p "$detach_dir/system.dae" /etc/honk/.system.dae.detach && mv -f /etc/honk/.system.dae.detach /etc/honk/system.dae && \
|
||||
cp -p "$detach_dir/config.dae" /etc/honk/.config.dae.detach && mv -f /etc/honk/.config.dae.detach /etc/honk/config.dae && \
|
||||
uci set "honk.main.lan_network=$lan_network" && uci set "honk.main.listen_port=$listen_port" && \
|
||||
uci set "honk.main.clash_api_enabled=$clash_api_enabled" && uci set "honk.main.clash_api_port=$clash_api_port" && \
|
||||
uci set "honk.main.clash_api_secret=$clash_api_secret" && uci commit honk && chmod 0600 /etc/config/honk; } || \
|
||||
{ [ "$old_running" = 1 ] && ! honk_lifecycle_start 30; }; then
|
||||
if detach_rollback; then detach_fail detach_failed_rolled_back; else logger -t honk "detach rollback failed; snapshot kept at $detach_dir"; trap 'honk_lock_release >/dev/null 2>&1' 0; detach_fail detach_rollback_failed; fi
|
||||
return 1
|
||||
fi
|
||||
# 草稿原本就是旧配置时跟着换,免得配置页一直提示「有未应用的草稿」。
|
||||
if [ -f /etc/honk/config-draft.dae ] && [ "$(sha256sum /etc/honk/config-draft.dae | awk '{print $1}')" = "$old_hash" ]; then
|
||||
cp -p /etc/honk/config.dae /etc/honk/.config-draft.detach && mv -f /etc/honk/.config-draft.detach /etc/honk/config-draft.dae
|
||||
fi
|
||||
echo '{"ok":true,"message":"config_detached"}'
|
||||
}
|
||||
|
||||
diagnose() {
|
||||
load_settings
|
||||
errors=""
|
||||
@@ -972,8 +1201,11 @@ case "$1" in
|
||||
repair)
|
||||
repair
|
||||
;;
|
||||
detach)
|
||||
detach
|
||||
;;
|
||||
*)
|
||||
echo "usage: $0 {check|diagnose|preflight|local-direct|initialize|settings|repair}" >&2
|
||||
echo "usage: $0 {check|diagnose|preflight|local-direct|initialize|settings|repair|detach}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
+14
-2
@@ -1,6 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
HONK_LIFECYCLE_CORE=/usr/bin/honk-core
|
||||
HONK_LIFECYCLE_HAVE_TIMEOUT=$(command -v timeout 2>/dev/null)
|
||||
|
||||
# 字段归属:自带 global/experimental → file;只 include system.dae(且写法规范)→ system;
|
||||
# 其余一律 unknown。写法不规范时不猜,直接拒绝。
|
||||
@@ -46,6 +47,15 @@ config_owner() {
|
||||
fi
|
||||
}
|
||||
|
||||
# 启动轮询用 0.25 秒粒度,核心就绪后尽快返回;没有 timeout 命令时退回 1 秒。
|
||||
honk_lifecycle_pause() {
|
||||
if [ -n "$HONK_LIFECYCLE_HAVE_TIMEOUT" ]; then
|
||||
timeout 0.25 sleep 1 2>/dev/null
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
}
|
||||
|
||||
honk_lifecycle_procd_pid() { ubus call service list '{"name":"honk"}' 2>/dev/null | jsonfilter -e '@.honk.instances.main.pid' 2>/dev/null
|
||||
}
|
||||
|
||||
@@ -227,6 +237,7 @@ honk_lifecycle_start() {
|
||||
# be told the start failed. Count distinct PIDs and give up early instead.
|
||||
hlp_seen_pid=
|
||||
hlp_attempts=0
|
||||
hlp_tick=0
|
||||
while [ "$hlp_timeout" -gt 0 ]; do
|
||||
hlp_new_pid=$(honk_lifecycle_managed_pid 2>/dev/null)
|
||||
if [ -n "$hlp_new_pid" ] && [ "$hlp_new_pid" != "$hlp_seen_pid" ]; then
|
||||
@@ -264,8 +275,9 @@ honk_lifecycle_start() {
|
||||
hlp_pid=
|
||||
hlp_starttime=
|
||||
fi
|
||||
sleep 1
|
||||
hlp_timeout=$((hlp_timeout - 1))
|
||||
honk_lifecycle_pause
|
||||
hlp_tick=$((hlp_tick + 1))
|
||||
[ "$hlp_tick" -lt 4 ] || { hlp_tick=0; hlp_timeout=$((hlp_timeout - 1)); }
|
||||
done
|
||||
echo "honk lifecycle: new managed core did not become stable and own its API listener" >&2
|
||||
return 1
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-honk
|
||||
PKG_VERSION:=2026.11.03
|
||||
PKG_VERSION:=2026.11.04
|
||||
PKG_RELEASE:=1
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
@@ -13,7 +13,7 @@ PKG_SOURCE:=doona-46e153cf-c6229ba102e8.tar.gz
|
||||
# Each package Makefile is evaluated on its own, so this tag is defined here too
|
||||
# instead of relying on the honk package's variable. It must match honk/Makefile:
|
||||
# scripts/check.sh enforces that.
|
||||
HONK_SOURCE_TAG?=source-41594075839
|
||||
HONK_SOURCE_TAG?=source-41792584995
|
||||
PKG_SOURCE_URL?=https://github.com/kenzok8/openwrt-honk/releases/download/$(HONK_SOURCE_TAG)
|
||||
PKG_SOURCE_SUBDIR:=doona
|
||||
PKG_HASH:=c6229ba102e867407540b3ec220595eff6ed63d7ae0037377432281977c9cf86
|
||||
|
||||
@@ -769,6 +769,10 @@ return view.extend({
|
||||
parts.push(E('span', {}, _('An old manual draft from a previous version is kept on disk.')));
|
||||
parts.push(draftButton(_('Load old draft'), 'legacy'));
|
||||
}
|
||||
if (status && status.detach_backup) {
|
||||
parts.push(E('span', {}, _('The configuration from before the switch is kept on disk.')));
|
||||
parts.push(draftButton(_('Load the previous configuration'), 'predetach'));
|
||||
}
|
||||
draftNote.textContent = '';
|
||||
if (!parts.length) {
|
||||
draftNote.hidden = true;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
'use strict';
|
||||
'require network';
|
||||
'require poll';
|
||||
'require ui';
|
||||
'require view';
|
||||
'require view.honk.rpc as honk';
|
||||
'require uci';
|
||||
@@ -246,7 +247,15 @@ return view.extend({
|
||||
form.appendChild(field(_('Clash API port'), clashPortInput));
|
||||
form.appendChild(field(_('Clash API secret'), clashSecretInput, status.clash_api_secret_set ? _('A secret is set. Leave blank to keep it.') : _('Required when enabling. Use 6–128 letters, numbers, dots, underscores or hyphens.')));
|
||||
form.appendChild(E('p', { 'class': 'honk-note' }, _('Use your existing HTTPS reverse proxy for external access. Enter the secret in Zashboard when connecting.')));
|
||||
form.appendChild(E('div', { 'class': 'cbi-page-actions' }, save));
|
||||
if (owner === 'file') {
|
||||
const detach = E('button', { 'class': 'cbi-button cbi-button-action', 'type': 'button' }, _('Switch to panel management'));
|
||||
detach.addEventListener('click', function() { detachSettings(detach); });
|
||||
form.appendChild(E('div', { 'class': 'honk-actions' }, [ detach ]));
|
||||
} else {
|
||||
form.appendChild(E('div', { 'class': 'cbi-page-actions' }, [ save ]));
|
||||
if (owner !== 'system')
|
||||
form.appendChild(E('p', { 'class': 'honk-note' }, _('This configuration is not a plain include of system.dae, so these settings cannot be edited here.')));
|
||||
}
|
||||
form.appendChild(message);
|
||||
form.addEventListener('submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
@@ -292,6 +301,77 @@ return view.extend({
|
||||
return card;
|
||||
}
|
||||
|
||||
/* 先预览会变动的项,确认后才真正改写。 */
|
||||
function detachSettings(button) {
|
||||
button.disabled = true;
|
||||
settingsBusy = true;
|
||||
actionMessage.textContent = _('Checking the configuration…');
|
||||
honk.configDetach(true).then(honk.ensureOk).then(function(preview) {
|
||||
actionMessage.textContent = '';
|
||||
return confirmDetach(preview);
|
||||
}).then(function(confirmed) {
|
||||
if (!confirmed)
|
||||
return;
|
||||
actionMessage.textContent = _('Switching to panel management…');
|
||||
return honk.configDetach(false).then(honk.ensureOk).then(function(result) {
|
||||
actionMessage.textContent = honk.resultMessage(result, _('The panel manages these settings now.'));
|
||||
});
|
||||
}).catch(function(error) {
|
||||
actionMessage.textContent = honk.errorMessage(error, _('The switch failed. The configuration was left unchanged.'));
|
||||
}).finally(function() {
|
||||
settingsBusy = false;
|
||||
button.disabled = false;
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
function confirmDetach(preview) {
|
||||
/* 面板能映射的项与它固定写死的项分开列,避免把「会被默认值覆盖」说成「不支持」。 */
|
||||
const mapped = {
|
||||
'global.lan_interface': _('LAN network'),
|
||||
'experimental.native_api.listen': _('Doona listen address'),
|
||||
'experimental.clash_api.external_controller': _('Clash API address')
|
||||
};
|
||||
const carried = [];
|
||||
const replaced = [];
|
||||
(preview.changes || []).forEach(function(item) {
|
||||
if (/\.secret$/.test(item.key))
|
||||
return;
|
||||
const label = mapped[item.key];
|
||||
if (label)
|
||||
carried.push(E('li', {}, '%s:%s → %s'.format(label, item.old, item.new)));
|
||||
else if (!item.new)
|
||||
replaced.push(E('li', {}, _('%s: %s (removed)').format(item.key, item.old)));
|
||||
else
|
||||
replaced.push(E('li', {}, '%s:%s → %s'.format(item.key, item.old, item.new)));
|
||||
});
|
||||
const summary = _('LAN network %s, port %s, Clash API %s.').format(preview.lan_network, preview.listen_port,
|
||||
preview.clash_api_enabled ? _('enabled on port %s').format(preview.clash_api_port) : _('disabled'));
|
||||
return new Promise(function(resolve) {
|
||||
ui.showModal(_('Switch to panel management'), [
|
||||
E('p', {}, _('These settings move to this page. Subscriptions, nodes, groups and routing rules stay as they are.')),
|
||||
E('p', {}, summary),
|
||||
carried.length ? E('div', {}, [ E('p', {}, _('These values carry over, written the way the panel expects:')), E('ul', {}, carried) ]) : '',
|
||||
replaced.length ? E('div', {}, [ E('p', {}, _('These items have no panel setting, so the panel defaults replace them:')), E('ul', {}, replaced) ]) : '',
|
||||
E('p', { 'class': 'honk-note' }, _('Honk restarts during the switch.')),
|
||||
E('p', { 'class': 'honk-note' }, _('The current file is backed up. To undo, load it again from the Configuration page and apply it.')),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', { 'class': 'cbi-button', 'click': function(ev) {
|
||||
ev.preventDefault();
|
||||
ui.hideModal();
|
||||
resolve(false);
|
||||
} }, [ _('Cancel') ]),
|
||||
' ',
|
||||
E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function(ev) {
|
||||
ev.preventDefault();
|
||||
ui.hideModal();
|
||||
resolve(true);
|
||||
} }, [ _('Switch to panel management') ])
|
||||
])
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
function renderSetup(status) {
|
||||
if (setupBusy)
|
||||
return;
|
||||
|
||||
@@ -68,6 +68,7 @@ const callLanCandidates = rpc.declare({ object: 'honk', method: 'lan_candidates'
|
||||
const callConfigValidate = rpc.declare({ object: 'honk', method: 'config_validate', params: [ 'content' ], expect: { '': {} } });
|
||||
const callConfigSave = rpc.declare({ object: 'honk', method: 'config_save', params: [ 'content' ], expect: { '': {} } });
|
||||
const callConfigApply = rpc.declare({ object: 'honk', method: 'config_apply', expect: { '': {} } });
|
||||
const callConfigDetach = rpc.declare({ object: 'honk', method: 'config_detach', params: [ 'dry_run' ], expect: { '': {} } });
|
||||
const callRepair = rpc.declare({ object: 'honk', method: 'repair', expect: { '': {} } });
|
||||
const callBackup = rpc.declare({ object: 'honk', method: 'backup', expect: { '': {} } });
|
||||
const callRestore = rpc.declare({ object: 'honk', method: 'restore', expect: { '': {} } });
|
||||
@@ -335,6 +336,12 @@ function tokenMessage(token) {
|
||||
config_validation_failed: _('The configuration did not pass static validation.'),
|
||||
config_validation_unavailable: _('The core validator did not return a result.'),
|
||||
config_settings_owned_by_file: _('这些设置由当前配置文件决定,请到「配置 → 编辑配置」里修改。'),
|
||||
config_detached: _('已改由面板接管,订阅和分流规则保持不变。'),
|
||||
config_not_file_owned: _('当前配置没有自带系统设置,不需要切换。'),
|
||||
detach_layout_unsupported: _('配置里的 global / experimental 写法无法自动拆出,请到「配置 → 编辑配置」手动删除。'),
|
||||
detach_validation_failed: _('拆分后的配置没有通过校验,原配置未改动。'),
|
||||
detach_failed_rolled_back: _('切换后服务没能启动,已还原原配置。'),
|
||||
detach_rollback_failed: _('切换失败且自动还原未完成,请到「维护」执行系统恢复。'),
|
||||
config_valid: _('配置通过校验。'),
|
||||
config_invalid: _('配置没有通过校验。'),
|
||||
config_saved_pending_apply: _('Configuration saved. The running service is unchanged until you apply it.'),
|
||||
@@ -465,6 +472,7 @@ return baseclass.extend({
|
||||
configValidate: callConfigValidate,
|
||||
configSave: callConfigSave,
|
||||
configApply: callConfigApply,
|
||||
configDetach: callConfigDetach,
|
||||
repair: callRepair,
|
||||
backup: callBackup,
|
||||
restore: callRestore,
|
||||
|
||||
@@ -18,6 +18,9 @@ msgstr "%s(错误码:%s)"
|
||||
msgid "%s imported and Honk is running."
|
||||
msgstr "%s 已导入,Honk 正在运行。"
|
||||
|
||||
msgid "%s: %s (removed)"
|
||||
msgstr "%s:%s(移除)"
|
||||
|
||||
msgid "/etc/honk/config.dae 不存在或不是普通文件。"
|
||||
msgstr "/etc/honk/config.dae 不存在或不是普通文件。"
|
||||
|
||||
@@ -159,6 +162,9 @@ msgstr "选择 LAN 网卡"
|
||||
msgid "Clash API %s"
|
||||
msgstr "Clash API %s"
|
||||
|
||||
msgid "Clash API address"
|
||||
msgstr "Clash API 地址"
|
||||
|
||||
msgid "Clash API port"
|
||||
msgstr "Clash API 端口"
|
||||
|
||||
@@ -288,6 +294,9 @@ msgstr "已载入 /etc/honk/config.dae 的当前配置。"
|
||||
msgid "Custom"
|
||||
msgstr "自定义"
|
||||
|
||||
msgid "DNS"
|
||||
msgstr "DNS"
|
||||
|
||||
msgid "Daily"
|
||||
msgstr "每天"
|
||||
|
||||
@@ -318,6 +327,9 @@ msgstr "Doona 面板 %s"
|
||||
msgid "Doona is not ready yet. Check the overview for status."
|
||||
msgstr "Doona 尚未就绪,请在概况页查看状态。"
|
||||
|
||||
msgid "Doona listen address"
|
||||
msgstr "Doona 监听地址"
|
||||
|
||||
msgid "Doona version"
|
||||
msgstr "Doona 版本"
|
||||
|
||||
@@ -459,6 +471,9 @@ msgstr "Honk 报告了系统问题:%s"
|
||||
msgid "Honk restarted."
|
||||
msgstr "Honk 已重启。"
|
||||
|
||||
msgid "Honk restarts during the switch."
|
||||
msgstr "切换过程中会重启 Honk。"
|
||||
|
||||
msgid "Honk settings"
|
||||
msgstr "Honk 设置"
|
||||
|
||||
@@ -525,6 +540,11 @@ msgstr "局域网接口 %s"
|
||||
msgid "LAN network"
|
||||
msgstr "LAN 网络"
|
||||
|
||||
msgid "LAN network %s, port %s, Clash API %s."
|
||||
msgstr "LAN 网络 %s,端口 %s,Clash API %s。"
|
||||
|
||||
|
||||
|
||||
msgid "Line %d"
|
||||
msgstr "第 %d 行"
|
||||
|
||||
@@ -540,6 +560,9 @@ msgstr "载入草稿"
|
||||
msgid "Load old draft"
|
||||
msgstr "载入旧草稿"
|
||||
|
||||
msgid "Load the previous configuration"
|
||||
msgstr "载入接管前的配置"
|
||||
|
||||
msgid "Loaded %s into the editor."
|
||||
msgstr "已把 %s 载入编辑器。"
|
||||
|
||||
@@ -855,6 +878,9 @@ msgstr "订阅已保存,检测到可用节点。"
|
||||
msgid "Subscriptions and nodes must use a protocol Honk supports; SSR is not supported."
|
||||
msgstr "订阅与节点需使用 Honk 支持的协议,不支持 SSR。"
|
||||
|
||||
msgid "Switching to panel management…"
|
||||
msgstr "正在改由面板接管…"
|
||||
|
||||
msgid "System check and repair"
|
||||
msgstr "系统检查与修复"
|
||||
|
||||
@@ -897,6 +923,9 @@ msgstr "The configuration file exceeds the 2 MiB limit."
|
||||
msgid "The configuration file is missing."
|
||||
msgstr "配置文件不存在。"
|
||||
|
||||
msgid "The configuration from before the switch is kept on disk."
|
||||
msgstr "面板接管前的配置已备份在路由器上。"
|
||||
|
||||
msgid "The configuration request is empty or invalid."
|
||||
msgstr "The configuration request is empty or invalid."
|
||||
|
||||
@@ -909,6 +938,9 @@ msgstr "The core validator did not return a result."
|
||||
msgid "The curl command is missing. Install the curl package and try again."
|
||||
msgstr "缺少 curl 命令,请先安装 curl 包(apk add curl 或 opkg install curl)后重试。"
|
||||
|
||||
msgid "The current file is backed up. To undo, load it again from the Configuration page and apply it."
|
||||
msgstr "原配置已备份。想撤回:到「配置」页点「载入接管前的配置」,再保存并应用。"
|
||||
|
||||
msgid "The dae file contains invalid sections or unsupported includes."
|
||||
msgstr "dae 文件包含无效配置段或不支持的 include。"
|
||||
|
||||
@@ -957,6 +989,9 @@ msgstr "Honk 托管服务状态异常。"
|
||||
msgid "The operation failed."
|
||||
msgstr "操作失败。"
|
||||
|
||||
msgid "The panel manages these settings now."
|
||||
msgstr "已改由面板接管,订阅和分流规则保持不变。"
|
||||
|
||||
msgid "The password must be between 8 and 128 characters."
|
||||
msgstr "密码长度必须在 8 到 128 个字符之间。"
|
||||
|
||||
@@ -1005,6 +1040,9 @@ msgstr "订阅无效或名称已存在。"
|
||||
msgid "The subscription returned no usable nodes. The configuration was not changed."
|
||||
msgstr "订阅没有返回可用节点,配置未改动。"
|
||||
|
||||
msgid "The switch failed. The configuration was left unchanged."
|
||||
msgstr "切换失败,原配置未改动。"
|
||||
|
||||
msgid "The system check did not finish (interface timeout or error). Retry, or check the core log under Maintenance → Logs."
|
||||
msgstr "系统检查没有跑完(接口超时或出错),请重试;或到「维护 → 日志」查看核心日志。"
|
||||
|
||||
@@ -1029,9 +1067,21 @@ msgstr "上传的 dae 文件无效。"
|
||||
msgid "There is no saved draft to apply."
|
||||
msgstr "没有可应用的已保存草稿。"
|
||||
|
||||
msgid "These items have no panel setting, so the panel defaults replace them:"
|
||||
msgstr "这些项面板没有对应设置,会改成面板默认值:"
|
||||
|
||||
msgid "These lines will be changed for this router:"
|
||||
msgstr "这几行会按本机修改:"
|
||||
|
||||
msgid "These settings move to this page. Subscriptions, nodes, groups and routing rules stay as they are."
|
||||
msgstr "这些设置改由概览页管理,订阅、节点、分组和分流规则不变。"
|
||||
|
||||
msgid "These values carry over, written the way the panel expects:"
|
||||
msgstr "这些值会保留,只是写法按面板要求调整:"
|
||||
|
||||
msgid "This configuration is not a plain include of system.dae, so these settings cannot be edited here."
|
||||
msgstr "当前配置不是纯粹的 system.dae 引用,这里无法编辑这些设置。"
|
||||
|
||||
msgid "This core cannot use any of these nodes, so the subscription was not saved."
|
||||
msgstr "这份订阅里的节点本内核一个都用不了,所以没有保存。"
|
||||
|
||||
@@ -1104,6 +1154,12 @@ msgstr "YAML 解析器加载失败"
|
||||
msgid "Zashboard is still downloading or unavailable."
|
||||
msgstr "Zashboard 仍在下载或暂不可用。"
|
||||
|
||||
msgid "disabled"
|
||||
msgstr "关闭"
|
||||
|
||||
msgid "enabled on port %s"
|
||||
msgstr "开启(端口 %s)"
|
||||
|
||||
msgid "global settings"
|
||||
msgstr "全局设置"
|
||||
|
||||
@@ -1209,6 +1265,12 @@ msgstr "写入 system.dae 失败,已回滚。"
|
||||
msgid "写入 system.dae 的监听地址失败,请检查文件权限。"
|
||||
msgstr "写入 system.dae 的监听地址失败,请检查文件权限。"
|
||||
|
||||
msgid "切换后服务没能启动,已还原原配置。"
|
||||
msgstr "切换后服务没能启动,已还原原配置。"
|
||||
|
||||
msgid "切换失败且自动还原未完成,请到「维护」执行系统恢复。"
|
||||
msgstr "切换失败且自动还原未完成,请到「维护」执行系统恢复。"
|
||||
|
||||
msgid "创建 Doona 管理员失败,请到「维护 → 日志」查看核心日志。"
|
||||
msgstr "创建 Doona 管理员失败,请到「维护 → 日志」查看核心日志。"
|
||||
|
||||
@@ -1233,12 +1295,27 @@ msgstr "备份现有 system.dae 失败,请检查磁盘空间。"
|
||||
msgid "备份系统配置失败,请检查磁盘空间。"
|
||||
msgstr "备份系统配置失败,请检查磁盘空间。"
|
||||
|
||||
msgid "已改由面板接管,订阅和分流规则保持不变。"
|
||||
msgstr "已改由面板接管,订阅和分流规则保持不变。"
|
||||
|
||||
msgid "已经初始化过了,请用原来的账号登录 Doona。忘记密码:到「维护 → 重置管理员」清掉管理员记录(不动订阅和规则),再回到本页重新初始化。"
|
||||
msgstr "已经初始化过了,请用原来的账号登录 Doona。忘记密码:到「维护 → 重置管理员」清掉管理员记录(不动订阅和规则),再回到本页重新初始化。"
|
||||
|
||||
msgid "应用设置失败且回滚未完成,快照已保留,请执行系统恢复。"
|
||||
msgstr "应用设置失败且回滚未完成,快照已保留,请执行系统恢复。"
|
||||
|
||||
msgid "开关类设置取值不合法。"
|
||||
msgstr "开关类设置取值不合法。"
|
||||
|
||||
msgid "当前配置不支持快捷追加,请在「编辑配置」中修改。"
|
||||
msgstr "当前配置不支持快捷追加,请在「编辑配置」中修改。"
|
||||
|
||||
msgid "当前配置没有 include { system.dae },这些设置请到「编辑配置」里修改。"
|
||||
msgstr "当前配置没有 include { system.dae },这些设置请到「编辑配置」里修改。"
|
||||
|
||||
msgid "当前配置没有自带系统设置,不需要切换。"
|
||||
msgstr "当前配置没有自带系统设置,不需要切换。"
|
||||
|
||||
msgid "找不到 /usr/bin/honk-core,请重新安装 honk 包。"
|
||||
msgstr "找不到 /usr/bin/honk-core,请重新安装 honk 包。"
|
||||
|
||||
@@ -1248,6 +1325,9 @@ msgstr "找不到该任务。"
|
||||
msgid "抓不到这个订阅地址:网络不通、需要代理,或链接已失效。"
|
||||
msgstr "抓不到这个订阅地址:网络不通、需要代理,或链接已失效。"
|
||||
|
||||
msgid "拆分后的配置没有通过校验,原配置未改动。"
|
||||
msgstr "拆分后的配置没有通过校验,原配置未改动。"
|
||||
|
||||
msgid "拿不到任务编号。"
|
||||
msgstr "拿不到任务编号。"
|
||||
|
||||
@@ -1344,35 +1424,29 @@ msgstr "读不到任务状态。"
|
||||
msgid "读不到核心的 API 状态,请查看日志。"
|
||||
msgstr "读不到核心的 API 状态,请查看日志。"
|
||||
|
||||
msgid "这些设置由当前配置文件决定,请到「配置 → 编辑配置」里修改。"
|
||||
msgstr "这些设置由当前配置文件决定,请到「配置 → 编辑配置」里修改。"
|
||||
|
||||
msgid "选中的 LAN 网络没有 IPv4 地址。"
|
||||
msgstr "选中的 LAN 网络没有 IPv4 地址。"
|
||||
|
||||
msgid "配置没有通过校验。"
|
||||
msgstr "配置没有通过校验。"
|
||||
|
||||
msgid "配置没有通过静态校验。"
|
||||
msgstr "配置没有通过静态校验。"
|
||||
|
||||
msgid "配置路径不是 /etc/honk/config.dae,拒绝操作。"
|
||||
msgstr "配置路径不是 /etc/honk/config.dae,拒绝操作。"
|
||||
|
||||
msgid "预检失败,请稍后重试。"
|
||||
msgstr "预检失败,请稍后重试。"
|
||||
|
||||
msgid "DNS"
|
||||
msgstr "DNS"
|
||||
|
||||
msgid "已经初始化过了,请用原来的账号登录 Doona。忘记密码:到「维护 → 重置管理员」清掉管理员记录(不动订阅和规则),再回到本页重新初始化。"
|
||||
msgstr "已经初始化过了,请用原来的账号登录 Doona。忘记密码:到「维护 → 重置管理员」清掉管理员记录(不动订阅和规则),再回到本页重新初始化。"
|
||||
|
||||
msgid "当前配置没有 include { system.dae },这些设置请到「编辑配置」里修改。"
|
||||
msgstr "当前配置没有 include { system.dae },这些设置请到「编辑配置」里修改。"
|
||||
|
||||
msgid "这些设置由当前配置文件决定,请到「配置 → 编辑配置」里修改。"
|
||||
msgstr "这些设置由当前配置文件决定,请到「配置 → 编辑配置」里修改。"
|
||||
|
||||
msgid "配置没有通过校验。"
|
||||
msgstr "配置没有通过校验。"
|
||||
|
||||
msgid "配置通过校验。"
|
||||
msgstr "配置通过校验。"
|
||||
|
||||
msgid "当前配置不支持快捷追加,请在「编辑配置」中修改。"
|
||||
msgstr "当前配置不支持快捷追加,请在「编辑配置」中修改。"
|
||||
msgid "配置里的 global / experimental 写法无法自动拆出,请到「配置 → 编辑配置」手动删除。"
|
||||
msgstr "配置里的 global / experimental 写法无法自动拆出,请到「配置 → 编辑配置」手动删除。"
|
||||
|
||||
msgid "预检失败,请稍后重试。"
|
||||
msgstr "预检失败,请稍后重试。"
|
||||
|
||||
msgid "Switch to panel management"
|
||||
msgstr "切换为面板管理"
|
||||
@@ -51,7 +51,7 @@ lock_owner_invalid() {
|
||||
}
|
||||
|
||||
status() {
|
||||
local enabled initialized boot_enabled running api_url api_ready core_version core_commit doona_version kernel lan_network listen_port errors listen clash_api_enabled clash_api_port clash_api_secret clash_api_url clash_api_ready clash_ui_ready clash_secret_set settings_owner api_enabled core_ready config_pending draft_pending legacy_draft clash_listen_addr
|
||||
local enabled initialized boot_enabled running api_url api_ready core_version core_commit doona_version kernel lan_network listen_port errors listen clash_api_enabled clash_api_port clash_api_secret clash_api_url clash_api_ready clash_ui_ready clash_secret_set settings_owner api_enabled core_ready config_pending draft_pending legacy_draft detach_backup clash_listen_addr
|
||||
config_load honk
|
||||
config_get_bool enabled main enabled 0
|
||||
config_get_bool initialized main initialized 0
|
||||
@@ -74,6 +74,7 @@ status() {
|
||||
config_pending=0
|
||||
draft_pending=0
|
||||
legacy_draft=0
|
||||
detach_backup=0
|
||||
listen=
|
||||
runtime_config=$("$CORE" config validate --config /etc/honk/config.dae 2>/dev/null)
|
||||
if [ -n "$runtime_config" ]; then
|
||||
@@ -120,6 +121,7 @@ status() {
|
||||
[ "$draft_hash" = "$active_hash" ] || draft_pending=1
|
||||
fi
|
||||
[ -f /etc/honk/manual-config.dae ] && [ ! -L /etc/honk/manual-config.dae ] && legacy_draft=1
|
||||
[ -f /etc/honk/config-before-detach.dae ] && [ ! -L /etc/honk/config-before-detach.dae ] && detach_backup=1
|
||||
clash_api_ready=0
|
||||
if [ "$running" = 1 ] && [ "$clash_api_enabled" = 1 ] && [ "$clash_api_port" -gt 0 ] && honk_lifecycle_listener_owned "$managed_pid" "$clash_api_port"; then clash_api_ready=1; fi
|
||||
clash_ui_ready=0
|
||||
@@ -155,6 +157,7 @@ status() {
|
||||
json_add_boolean config_pending "$config_pending"
|
||||
json_add_boolean draft_pending "$draft_pending"
|
||||
json_add_boolean legacy_draft "$legacy_draft"
|
||||
json_add_boolean detach_backup "$detach_backup"
|
||||
json_add_boolean core_ready "$core_ready"
|
||||
json_add_boolean api_enabled "$api_enabled"
|
||||
json_add_boolean api_ready "$api_ready"
|
||||
@@ -912,6 +915,7 @@ config_draft_read() {
|
||||
case "$draft_source" in
|
||||
draft) draft_file=/etc/honk/config-draft.dae ;;
|
||||
legacy) draft_file=/etc/honk/manual-config.dae ;;
|
||||
predetach) draft_file=/etc/honk/config-before-detach.dae ;;
|
||||
*) emit_result false invalid_request; return 1 ;;
|
||||
esac
|
||||
[ -f "$draft_file" ] && [ ! -L "$draft_file" ] || { emit_result false config_draft_missing; return 1; }
|
||||
@@ -1132,7 +1136,7 @@ config_apply() {
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{"status":{},"check":{},"initialize":{"username":"String","password":"String"},"start":{},"stop":{},"restart":{},"settings":{"lan_network":"String","listen_port":"Integer","boot_enabled":"Boolean","clash_api_enabled":"Boolean","clash_api_port":"Integer","clash_api_secret":"String"},"add_subscription":{"name":"String","url":"String"},"probe_subscription":{"url":"String"},"reset_admin":{},"add_nodes":{"links":"String"},"import_dae":{},"config_read":{},"config_draft_read":{},"lan_candidates":{},"config_validate":{"content":"String"},"config_save":{"content":"String"},"config_apply":{},"repair":{},"backup":{},"restore":{},"restore_prepare":{},"reset":{},"job_status":{"job_id":"String"},"logs":{"source":"String","level":"String","limit":"Integer"},"update_check":{},"update_apply":{}}'
|
||||
echo '{"status":{},"check":{},"initialize":{"username":"String","password":"String"},"start":{},"stop":{},"restart":{},"settings":{"lan_network":"String","listen_port":"Integer","boot_enabled":"Boolean","clash_api_enabled":"Boolean","clash_api_port":"Integer","clash_api_secret":"String"},"add_subscription":{"name":"String","url":"String"},"probe_subscription":{"url":"String"},"reset_admin":{},"add_nodes":{"links":"String"},"import_dae":{},"config_read":{},"config_draft_read":{},"lan_candidates":{},"config_validate":{"content":"String"},"config_save":{"content":"String"},"config_apply":{},"config_detach":{"dry_run":"Boolean"},"repair":{},"backup":{},"restore":{},"restore_prepare":{},"reset":{},"job_status":{"job_id":"String"},"logs":{"source":"String","level":"String","limit":"Integer"},"update_check":{},"update_apply":{}}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
@@ -1181,6 +1185,9 @@ list)
|
||||
config_apply)
|
||||
config_apply
|
||||
;;
|
||||
config_detach)
|
||||
exec "$CONTROL" detach
|
||||
;;
|
||||
repair)
|
||||
exec "$CONTROL" repair
|
||||
;;
|
||||
|
||||
@@ -81,7 +81,8 @@
|
||||
"update_apply",
|
||||
"config_read",
|
||||
"config_draft_read",
|
||||
"config_apply"
|
||||
"config_apply",
|
||||
"config_detach"
|
||||
]
|
||||
},
|
||||
"file": {
|
||||
|
||||
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_NAME:=luci-app-ssr-plus
|
||||
PKG_VERSION:=198
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||
|
||||
@@ -159,6 +159,10 @@ body[data-bg] .login-card {
|
||||
|
||||
.login-error {
|
||||
@apply bg-danger-surface text-danger border-hairline rounded-2xl border px-3 py-2 text-center text-sm;
|
||||
|
||||
&.warning {
|
||||
@apply bg-warning-surface text-warning;
|
||||
}
|
||||
}
|
||||
|
||||
.login-actions {
|
||||
|
||||
@@ -19,7 +19,8 @@ test("production assets stay within raw-transfer budgets", () => {
|
||||
|
||||
// 187K: pseudo-elements are nested rules, one content declaration each.
|
||||
assert.ok(main <= 187_000, "main.css exceeds 187 KB");
|
||||
assert.ok(login <= 12_000, "login.css exceeds 12 KB");
|
||||
// 12.5K: auth_plugin warning tokens added ~0.35 KB.
|
||||
assert.ok(login <= 12_500, "login.css exceeds 12.5 KB");
|
||||
// 22K: palette recents (record on pick, pure-LRU browse order, storage
|
||||
// validation) added ~0.8 KB and the ">" logout command ~0.2 KB. The total
|
||||
// budget below moved by the same amount.
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
|
||||
PKG_VERSION:=1.4.2
|
||||
PKG_VERSION:=1.4.3
|
||||
PKG_RELEASE:=20261104
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-content:"";--tw-shadow:0 0 #0000;--tw-inset-shadow:0 0 #0000;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-shadow:0 0 #0000;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-2xl:calc(var(--radius-base) * 2);--animate-spin:spin 1s linear infinite}}@layer base{*,:before,:after{box-sizing:border-box}body,hr,p,button,input{margin:0}button,input{font:inherit}a{text-decoration-line:none}[hidden]{display:none!important}}@layer components,utilities;:root{--bg:#f7fafc;--surface:#fff;--text:#121a22;--brand:#0085b7;--on-brand:#fff;--danger:#8d1925;--text-muted:#5f666d;--text-subtle:#7f858b;--hairline:#121a2221;--brand-subtle:#ddecf5;--danger-surface:#ffe8e6;--control-bg:#fff;--app-shadow-md:0 4px 16px #00000014, 0 1px 3px #0000000a;--font-sans:"Lato", ui-sans-serif, system-ui, sans-serif;--spacing:.25rem;--radius-base:.5rem}@supports (color:lab(0% 0 0)){:root{--bg:lab(98.1553% -.760227 -1.25223);--surface:lab(100% 0 0);--text:lab(8.89727% -1.68239 -6.93074);--brand:lab(51.3503% -19.237 -40.2116);--on-brand:lab(100% 0 0);--danger:lab(31.1495% 47.9256 25.8661);--text-muted:lab(42.8254% -1.57365 -4.8763);--text-subtle:lab(55.3248% -1.4126 -4.053);--hairline:lab(8.89727% -1.68239 -6.93074/.13);--brand-subtle:lab(92.5794% -3.9458 -6.17858);--danger-surface:lab(93.849% 11.2841 5.29771);--control-bg:lab(100.011% -.760317 -1.25225);--app-shadow-md:0 4px 16px lab(0% 0 0/.08), 0 1px 3px lab(0% 0 0/.04)}}[data-darkmode=true]{--bg:#05070e;--surface:#141822;--text:#f9fafb;--brand:#00958e;--on-brand:#fff;--danger:#f17070;--text-muted:#909297;--text-subtle:#5d6067;--hairline:#f9fafb1a;--brand-subtle:#061a20;--danger-surface:#541f20;--control-bg:#0a0e17;--app-shadow-md:0 10px 28px #0000006b}@supports (color:lab(0% 0 0)){[data-darkmode=true]{--bg:lab(1.93894% .0855997 -3.22397);--surface:lab(8.24713% -.0405461 -7.31381);--text:lab(98.252% -.0618994 -.756907);--brand:lab(55.0978% -44.5567 -7.62978);--on-brand:lab(100% 0 0);--danger:lab(63.5705% 50.8105 25.3639);--text-muted:lab(60.5379% -.225931 -3.05105);--text-subtle:lab(40.6865% -.278354 -4.23647);--hairline:lab(98.252% -.0618994 -.756907/.1);--brand-subtle:lab(7.95734% -6.34954 -6.72844);--danger-surface:lab(20.3014% 25.4342 12.827);--control-bg:lab(3.9802% .0812709 -5.64623);--app-shadow-md:0 10px 28px lab(0% 0 0/.42)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-property:none!important;animation:none!important}.spinning:before{animation:var(--animate-spin)!important}}html{background-color:var(--bg);min-height:100dvh;font-family:var(--font-sans);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}html[data-darkmode=true]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}html body{background-color:var(--bg);min-height:100dvh}.page-bg{pointer-events:none;background-image:var(--page-bg-lqip,var(--page-bg));background-position:50%;background-repeat:no-repeat;background-size:cover;position:fixed;inset:0;transform:translateZ(0)}.page-bg:after{pointer-events:none;background-image:var(--page-bg);opacity:0;--tw-content:"";content:var(--tw-content);background-position:50%;background-repeat:no-repeat;background-size:cover;transition:opacity .5s;position:absolute;inset:0}.page-bg.full-loaded:after{opacity:1}@keyframes divider-in{0%{opacity:0;transform:scaleX(.15)}}.login-screen{min-height:100dvh;padding-inline:calc(var(--spacing) * 5);padding-bottom:calc(var(--spacing) * 14);flex-direction:column;justify-content:center;align-items:center;display:flex;position:relative}.login-shell{width:100%;max-width:var(--container-md)}.page-bg{--page-bg:var(--login-bg);--page-bg-lqip:var(--login-bg-lqip)}body[data-bg] .page-bg:before{pointer-events:none;z-index:1;--tw-content:"";content:var(--tw-content);background:var(--bg);position:absolute;inset:0}@supports (color:color-mix(in lab, red, red)){body[data-bg] .page-bg:before{background:color-mix(in srgb,var(--bg) var(--login-bg-scrim,0%),transparent)}}body[data-bg] .login-card{-webkit-backdrop-filter:blur(var(--login-bg-blur,0px)) saturate(150%);backdrop-filLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-content:"";--tw-shadow:0 0 #0000;--tw-inset-shadow:0 0 #0000;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-shadow:0 0 #0000;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-2xl:calc(var(--radius-base) * 2);--animate-spin:spin 1s linear infinite}}@layer base{*,:before,:after{box-sizing:border-box}body,hr,p,button,input{margin:0}button,input{font:inherit}a{text-decoration-line:none}[hidden]{display:none!important}}@layer components,utilities;:root{--bg:#f7fafc;--surface:#fff;--text:#121a22;--brand:#0085b7;--on-brand:#fff;--warning:#643e00;--danger:#8d1925;--text-muted:#5f666d;--text-subtle:#7f858b;--hairline:#121a2221;--brand-subtle:#ddecf5;--warning-surface:#ffedd6;--danger-surface:#ffe8e6;--control-bg:#fff;--app-shadow-md:0 4px 16px #00000014, 0 1px 3px #0000000a;--font-sans:"Lato", ui-sans-serif, system-ui, sans-serif;--spacing:.25rem;--radius-base:.5rem}@supports (color:lab(0% 0 0)){:root{--bg:lab(98.1553% -.760227 -1.25223);--surface:lab(100% 0 0);--text:lab(8.89727% -1.68239 -6.93074);--brand:lab(51.3503% -19.237 -40.2116);--on-brand:lab(100% 0 0);--warning:lab(30.2236% 14.0359 42.8654);--danger:lab(31.1495% 47.9256 25.8661);--text-muted:lab(42.8254% -1.57365 -4.8763);--text-subtle:lab(55.3248% -1.4126 -4.053);--hairline:lab(8.89727% -1.68239 -6.93074/.13);--brand-subtle:lab(92.5794% -3.9458 -6.17858);--warning-surface:lab(94.8007% 4.75499 15.6111);--danger-surface:lab(93.849% 11.2841 5.29771);--control-bg:lab(100.011% -.760317 -1.25225);--app-shadow-md:0 4px 16px lab(0% 0 0/.08), 0 1px 3px lab(0% 0 0/.04)}}[data-darkmode=true]{--bg:#05070e;--surface:#141822;--text:#f9fafb;--brand:#00958e;--on-brand:#fff;--warning:#f0ba59;--danger:#f17070;--text-muted:#909297;--text-subtle:#5d6067;--hairline:#f9fafb1a;--brand-subtle:#061a20;--warning-surface:#45310b;--danger-surface:#541f20;--control-bg:#0a0e17;--app-shadow-md:0 10px 28px #0000006b}@supports (color:lab(0% 0 0)){[data-darkmode=true]{--bg:lab(1.93894% .0855997 -3.22397);--surface:lab(8.24713% -.0405461 -7.31381);--text:lab(98.252% -.0618994 -.756907);--brand:lab(55.0978% -44.5567 -7.62978);--on-brand:lab(100% 0 0);--warning:lab(79.1996% 12.6808 55.9505);--danger:lab(63.5705% 50.8105 25.3639);--text-muted:lab(60.5379% -.225931 -3.05105);--text-subtle:lab(40.6865% -.278354 -4.23647);--hairline:lab(98.252% -.0618994 -.756907/.1);--brand-subtle:lab(7.95734% -6.34954 -6.72844);--warning-surface:lab(22.2973% 6.18184 26.7927);--danger-surface:lab(20.3014% 25.4342 12.827);--control-bg:lab(3.9802% .0812709 -5.64623);--app-shadow-md:0 10px 28px lab(0% 0 0/.42)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-property:none!important;animation:none!important}.spinning:before{animation:var(--animate-spin)!important}}html{background-color:var(--bg);min-height:100dvh;font-family:var(--font-sans);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}html[data-darkmode=true]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}html body{background-color:var(--bg);min-height:100dvh}.page-bg{pointer-events:none;background-image:var(--page-bg-lqip,var(--page-bg));background-position:50%;background-repeat:no-repeat;background-size:cover;position:fixed;inset:0;transform:translateZ(0)}.page-bg:after{pointer-events:none;background-image:var(--page-bg);opacity:0;--tw-content:"";content:var(--tw-content);background-position:50%;background-repeat:no-repeat;background-size:cover;transition:opacity .5s;position:absolute;inset:0}.page-bg.full-loaded:after{opacity:1}@keyframes divider-in{0%{opacity:0;transform:scaleX(.15)}}.login-screen{min-height:100dvh;padding-inline:calc(var(--spacing) * 5);padding-bottom:calc(var(--spacing) * 14);flex-direction:column;justify-content:center;align-items:center;display:flex;position:relative}.login-shell{width:100%;max-width:var(--container-md)}.page-bg{--page-bg:var(--login-bg);--page-bg-lqip:var(--login-bg-lqip)}body[data-bg] .page-bg:before{pointer-events:none;z-index:1;--tw-content:"";content:var(--tw-content);background:var(--bg);position:absolute;inset:Line truncated
|
||||
@@ -54,12 +54,52 @@ const loginIconVer = themeTokens.icon_cache_version || '0';
|
||||
<input name="luci_password" id="luci_password" type="password" class="login-input" autocomplete="current-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (auth_fields): %}
|
||||
{% for (let field in auth_fields): %}
|
||||
<div class="login-field">
|
||||
<label class="login-label" for="{{ field.name }}">{{ _(field.label ?? field.name) }}</label>
|
||||
<div class="input-wrap">
|
||||
<input
|
||||
name="{{ field.name }}"
|
||||
id="{{ field.name }}"
|
||||
type="{{ field.type ?? 'text' }}"
|
||||
class="login-input"
|
||||
{% if (field.placeholder): %}placeholder="{{ field.placeholder }}"{% endif %}
|
||||
{% if (field.inputmode): %}inputmode="{{ field.inputmode }}"{% endif %}
|
||||
{% if (field.pattern): %}pattern="{{ field.pattern }}"{% endif %}
|
||||
{% if (field.maxlength): %}maxlength="{{ field.maxlength }}"{% endif %}
|
||||
{% if (field.autocomplete): %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if (field.required): %}required{% endif %}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if (auth_html): %}
|
||||
<div class="login-field">
|
||||
{{ auth_html }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if (auth_assets): %}
|
||||
{% for (let asset in auth_assets): %}
|
||||
{% if (asset.type == 'script'): %}
|
||||
<script src="{{ asset.src }}"></script>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if (fuser): %}
|
||||
<div class="login-error" role="alert">{{ _('Invalid username and/or password! Please try again.') }}</div>
|
||||
{% if (auth_message || fuser): %}
|
||||
<div class="login-error{% if (auth_plugin): %} warning{% endif %}" role="alert">
|
||||
{{ auth_message || _('Invalid username and/or password! Please try again.') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<input type="hidden" name="luci_magic" value="{{ luci_magic }}">
|
||||
|
||||
<div class="login-actions">
|
||||
<button type="submit" class="login-submit">{{ _('Log in') }}</button>
|
||||
</div>
|
||||
|
||||
@@ -42,9 +42,11 @@ function run(records) {
|
||||
* document that was never that short. Written before the tick (a pin in the same statement
|
||||
* sequence is never laid out: 1882px still clamped away) and on the containers, never the column,
|
||||
* where it suppresses the engine's own anchoring (css-scroll-anchoring-1 §2.2.2) and cost 120px of
|
||||
* growth, all 120px. The selector covers what `dom.content()` empties: a section body and a table
|
||||
* (a table's body climbs to the same box). */
|
||||
const SHRINKS = '.cbi-section > div, .table';
|
||||
* growth, all 120px. The selector covers what `dom.content()` empties: a section body, a table
|
||||
* (a table's body climbs to the same box), and luci-mod-dashboard's KPI row, charts and tab map,
|
||||
* which sit straight under `.Dashboard`. Without their floor WebKit 27.2 put a reader 526px down
|
||||
* the dashboard at 0 on every poll, at 1280, 820 and 390px alike. */
|
||||
const SHRINKS = '.cbi-section > div, .table, .Dashboard > div';
|
||||
|
||||
const FLOORED = '[data-fs-floor]';
|
||||
|
||||
@@ -149,6 +151,9 @@ function scrollTop() {
|
||||
const sc = scroller();
|
||||
return sc ? sc.scrollTop : window.scrollY;
|
||||
}
|
||||
function docHeight() {
|
||||
return (scroller() || document.documentElement).scrollHeight;
|
||||
}
|
||||
|
||||
function scrolling() { return Date.now() < _movingUntil; }
|
||||
function sampleMotion() {
|
||||
@@ -276,7 +281,7 @@ function rememberRest(force) {
|
||||
const ref = anchorRef();
|
||||
_restAt = scrollTop();
|
||||
_restPage = pageStamp();
|
||||
_rest = ref ? { el: ref.el, top: ref.top, at: _restAt, sec: ref.sec, secTop: ref.secTop } : null;
|
||||
_rest = ref ? { el: ref.el, top: ref.top, at: _restAt, sec: ref.sec, secTop: ref.secTop, tall: ref.tall } : null;
|
||||
}
|
||||
|
||||
function anchorFor() {
|
||||
@@ -285,11 +290,11 @@ function anchorFor() {
|
||||
if (!_rest || !_rest.el.isConnected) {
|
||||
if (clamped) return { by: _restAt - at };
|
||||
if (_rest && _rest.sec && _rest.sec.isConnected && at === _restAt)
|
||||
return { el: _rest.sec, top: _rest.secTop, slack: 0 };
|
||||
return { el: _rest.sec, top: _rest.secTop, slack: 0, tall: _rest.tall };
|
||||
return anchorRef();
|
||||
}
|
||||
if (at !== _rest.at && !clamped) return null;
|
||||
return { el: _rest.el, top: _rest.top, slack: Math.max(0, _rest.at - at) };
|
||||
return { el: _rest.el, top: _rest.top, slack: Math.max(0, _rest.at - at), tall: _rest.tall };
|
||||
}
|
||||
|
||||
function anchorRef() {
|
||||
@@ -326,7 +331,7 @@ function anchorRef() {
|
||||
keep = keep.parentElement;
|
||||
if (keep === host) keep = null;
|
||||
return { el, top: el.getBoundingClientRect().top,
|
||||
sec: keep, secTop: keep ? keep.getBoundingClientRect().top : 0 };
|
||||
sec: keep, secTop: keep ? keep.getBoundingClientRect().top : 0, tall: docHeight() };
|
||||
}
|
||||
|
||||
let _anchorPending = null;
|
||||
@@ -375,7 +380,11 @@ function applyAnchor(ref) {
|
||||
if (!ref.el.isConnected) return;
|
||||
const drift = ref.el.getBoundingClientRect().top - ref.top;
|
||||
if (Math.abs(drift) < 1) return; /* nothing needed correcting here */
|
||||
if (Math.abs(drift) > (window.innerHeight || 0) + 200 + (ref.slack || 0)) return;
|
||||
/* The cap: one viewport, or what the document grew or shrank since `ref`, whichever is more. A
|
||||
* subtree swap moves a reference by thousands while the height holds; five leases at 390px move it
|
||||
* 1290px, past an 844px viewport's 1044px, and the document grew all 1290. */
|
||||
const grew = Math.abs(docHeight() - ref.tall);
|
||||
if (Math.abs(drift) > Math.max(window.innerHeight || 0, grew) + 200 + (ref.slack || 0)) return;
|
||||
writeOffset(at + drift);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,6 +176,21 @@ function bodyLittered() {
|
||||
return littered;
|
||||
}
|
||||
|
||||
/* ---- a flushed menu makes the next navigation a full load ----
|
||||
* luci-app-package-manager calls ui.menu.flushCache() after an install, upgrade or removal. The
|
||||
* menu tree and every view class this document required may then be stale: a revisit would rerun
|
||||
* the old module. */
|
||||
let _menuFlushed = false;
|
||||
function watchMenuFlush() {
|
||||
const menu = ui.menu;
|
||||
if (!menu || typeof menu.flushCache !== 'function') return;
|
||||
const flush = menu.flushCache;
|
||||
menu.flushCache = function () {
|
||||
_menuFlushed = true;
|
||||
return flush.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
let _curPath = window.location.pathname;
|
||||
let _curSearch = window.location.search;
|
||||
let _navGen = 0;
|
||||
@@ -496,6 +511,8 @@ function navigate(pathname, push, kbd) {
|
||||
|
||||
if (bodyLittered()) return false;
|
||||
|
||||
if (_menuFlushed) return false;
|
||||
|
||||
/* an extension's lodash/underscore replaced window._: LuCI's own `_('x').format()` then throws on every view; a full load restores it */
|
||||
if (typeof _ !== 'function' || typeof _('') !== 'string') return false;
|
||||
|
||||
@@ -798,6 +815,7 @@ document.addEventListener('poll-stop', () => {
|
||||
});
|
||||
|
||||
watchSession();
|
||||
watchMenuFlush();
|
||||
|
||||
const _navCbs = [];
|
||||
function onNavigate(fn) { if (typeof fn === 'function') _navCbs.push(fn); }
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
/* 600px is cramped for a table form (.cbi-map cards on a big screen); vw widens toward 1100 on desktop only */
|
||||
.modal:has(.cbi-map) { max-width: min(68.75rem, 94vw); }
|
||||
/* a command log (Package Manager) wrapped at 70 mono columns in 600px; 900px holds 108 (13px JetBrains Mono) */
|
||||
.modal:has(> pre) { max-width: min(56.25rem, 94vw); }
|
||||
|
||||
/* no HORIZONTAL scroll ever (measured 634px scroll vs 530px room) — docs/css.md "Modal: no horizontal scroll, ever" */
|
||||
#modal_overlay .modal label.btn {
|
||||
|
||||
@@ -140,8 +140,13 @@ html {
|
||||
|
||||
/* ── Form elements ── */
|
||||
|
||||
.login-error {
|
||||
.login-error,
|
||||
.alert-message {
|
||||
@apply border-destructive-a30 bg-destructive-a8 text-destructive mb-4 rounded-2xl border px-4 py-3 text-sm;
|
||||
|
||||
&.warning {
|
||||
@apply border-warning-a30 bg-warning-a10 text-warning-soft-fg;
|
||||
}
|
||||
}
|
||||
|
||||
.login-field {
|
||||
@@ -162,7 +167,11 @@ html {
|
||||
}
|
||||
|
||||
input {
|
||||
@apply text-foreground placeholder:text-muted-foreground w-full bg-transparent py-3.5 pr-10 pl-9 text-sm focus:outline-none;
|
||||
@apply text-foreground placeholder:text-muted-foreground w-full bg-transparent px-4 py-3.5 text-sm focus:outline-none;
|
||||
}
|
||||
|
||||
.login-input-icon ~ input {
|
||||
@apply pl-9;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=A modern sidebar LuCI theme for OpenWrt, built with shadcn/ui design language.
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
|
||||
PKG_VERSION:=0.6.2
|
||||
PKG_VERSION:=0.6.3
|
||||
PKG_RELEASE:=20261104
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--radius-2xl:1rem;--radius-3xl:1.5rem;}}@layer base{*,:before,:after{box-sizing:border-box}body,p{margin:calc(var(--spacing) * 0)}button,input{margin:calc(var(--spacing) * 0);border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);color:inherit;font:inherit;background-color:#0000;border-width:0}::placeholder{opacity:1}[hidden]{display:none!important}}@layer components,utilities;@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-cyrillic-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-cyrillic-wght-normal.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-greek-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+1F??}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-greek-wght-normal.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-vietnamese-wght-normal.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-latin-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--background:oklch(98.5% .004 275);--foreground:oklch(18% .02 275);--primary:oklch(56.7% .158 275);--primary-foreground:oklch(100% 0 0);--destructive:oklch(55% .2 25);--border:oklch(89.65% .0058 275);--muted-foreground:oklch(52.62% .0131 275);--primary-hover:oklch(62.7% .158 275);--primary-a10:oklch(56.7% .158 275/.1);--primary-a20:oklch(56.7% .158 275/.2);--primary-a70:oklch(56.7% .158 275/.7);--destructive-a8:oklch(55% .2 25/.08);--destructive-a30:oklch(55% .2 25/.3);--foreground-a5:oklch(18% .02 275/.05);--font-sans:"Inter Variable", ui-sans-serif, system-ui, sans-serif;}[data-darkmode=true]{--background:oklch(15.9% .005 264);--foreground:oklch(90% .006 264);--primary:oklch(56.7% .158 275);--primary-foreground:oklch(100% 0 0);--destructive:oklch(63% .16 25);--border:oklch(26.27% .0051 264);--muted-foreground:oklch(58.14% .0056 264);--primary-hover:oklch(62.7% .158 275);--primary-a10:oklch(56.7% .158 275/.1);--primary-a20:oklch(56.7% .158 275/.2);--primary-a70:oklch(56.7% .158 275/.7);--destructive-a8:oklch(63% .16 25/.08);--destructive-a30:oklch(63% .16 25/.3);--foreground-a5:oklch(90% .006 264/.05);}html{color-scheme:light}html[data-darkmode=true]{color-scheme:dark}.card-header img,.login-input-icon img,.login-pw-toggle img,img.shadcn-icon{filter:brightness(0)saturate()}html[data-darkmode=true] .card-header img,html[data-darkmode=true] .login-input-icon img,html[data-darkmode=true] .login-pw-toggle img,html[data-darkmode=true] img.shadcn-icon{filter:brightness(0)saturate()invert()}.card-header img,img.shadcn-icon,#sidebar img.shadcn-icon{opacity:.65}html[data-darkmode=true] #sidebar img.shadcn-icon{opacity:.7}.card-header button:hover img,#sidebar .sidebar-nav-item.active img,#sidebar .sidebar-accordion-item.active img,#sidebar .sidebar-nav-parent:hover img{opacity:1}html{background-color:var(--background);font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--foreground)}@keyframes fadeSlideIn{to{opacity:1;transform:translateY(0)}}@keyframes slideRightIn{to{opacity:1;transfLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--radius-2xl:1rem;--radius-3xl:1.5rem;}}@layer base{*,:before,:after{box-sizing:border-box}body,p{margin:calc(var(--spacing) * 0)}button,input{margin:calc(var(--spacing) * 0);border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);color:inherit;font:inherit;background-color:#0000;border-width:0}::placeholder{opacity:1}[hidden]{display:none!important}}@layer components,utilities;@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-cyrillic-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-cyrillic-wght-normal.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-greek-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+1F??}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-greek-wght-normal.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-vietnamese-wght-normal.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-latin-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--background:oklch(98.5% .004 275);--foreground:oklch(18% .02 275);--primary:oklch(56.7% .158 275);--primary-foreground:oklch(100% 0 0);--destructive:oklch(55% .2 25);--border:oklch(89.65% .0058 275);--muted-foreground:oklch(52.62% .0131 275);--primary-hover:oklch(62.7% .158 275);--warning-soft-fg:oklch(42% .12 75);--primary-a10:oklch(56.7% .158 275/.1);--primary-a20:oklch(56.7% .158 275/.2);--primary-a70:oklch(56.7% .158 275/.7);--destructive-a8:oklch(55% .2 25/.08);--destructive-a30:oklch(55% .2 25/.3);--foreground-a5:oklch(18% .02 275/.05);--warning-a10:oklch(70% .13 75/.1);--warning-a30:oklch(70% .13 75/.3);--font-sans:"Inter Variable", ui-sans-serif, system-ui, sans-serif;}[data-darkmode=true]{--background:oklch(15.9% .005 264);--foreground:oklch(90% .006 264);--primary:oklch(56.7% .158 275);--primary-foreground:oklch(100% 0 0);--destructive:oklch(63% .16 25);--border:oklch(26.27% .0051 264);--muted-foreground:oklch(58.14% .0056 264);--primary-hover:oklch(62.7% .158 275);--warning-soft-fg:oklch(86% .1 85);--primary-a10:oklch(56.7% .158 275/.1);--primary-a20:oklch(56.7% .158 275/.2);--primary-a70:oklch(56.7% .158 275/.7);--destructive-a8:oklch(63% .16 25/.08);--destructive-a30:oklch(63% .16 25/.3);--foreground-a5:oklch(90% .006 264/.05);--warning-a10:oklch(80% .12 80/.1);--warning-a30:oklch(80% .12 80/.3);}html{color-scheme:light}html[data-darkmode=true]{color-scheme:dark}.card-header img,.login-input-icon img,.login-pw-toggle img,img.shadcn-icon{filter:brightness(0)saturate()}html[data-darkmode=true] .card-header img,html[data-darkmode=true] .login-input-icon img,html[data-darkmode=true] .login-pw-toggle img,html[data-darkmode=true] img.shadcn-icon{filter:brightness(0)saturate()invert()}.card-header img,img.shadcn-icon,#sidebar img.shadcn-icon{opacity:.65}html[data-darkmode=true] #sidebar img.shadcn-icon{opacity:.7}.card-header button:hover img,#sidebar .sidebar-nav-item.active img,#sidebar .sidebar-accordion-item.active img,#sidebar .sidebar-nav-parent:hover img{opacity:1}html{background-color:var(--background);font-family:var(--font-sans);Line truncated
|
||||
@@ -8,8 +8,8 @@
|
||||
import { getuid, getspnam } from 'luci.core';
|
||||
import { lsdir } from 'fs';
|
||||
|
||||
const boardinfo = ubus.call('system', 'board') ?? {};
|
||||
const rootsp = getspnam('root');
|
||||
const boardinfo = prefetched_boardinfo ?? (ubus.call('system', 'board') ?? {});
|
||||
const rootsp = !blank_page ? getspnam('root') : null;
|
||||
const root_pw_unset = (rootsp && rootsp.pwdp == '');
|
||||
|
||||
const page_segs = length(ctx?.request_path) ? ctx.request_path : (ctx?.path || []);
|
||||
@@ -82,8 +82,10 @@
|
||||
{% if (css): %}
|
||||
<style data-luci-shell>{{ css }}</style>
|
||||
{% endif %}
|
||||
{% if (!blank_page): %}
|
||||
<script src="{{ dispatcher.build_url('admin/translations', dispatcher.lang) }}"></script>
|
||||
<script src="{{ resource }}/cbi.js"></script>
|
||||
{% endif %}
|
||||
</head>
|
||||
|
||||
<body class="lang_{{ dispatcher.lang }}" data-page="{{ entityencode(page, true) }}" data-asset-version="{# PKG_VERSION #}"{% if (!blank_page): %} data-nav-stamp="{{ entityencode(join(':', [dispatcher.lang, ctx?.authuser ?? '', ctx?.authsession ?? '']), true) }}:{# PKG_VERSION #}"{% endif %}{% if (length(patch_all)): %} data-patches="{{ entityencode(join(' ', patch_all), true) }}"{% endif %}>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{%const boardinfo = ubus.call('system', 'board') ?? {};-%}
|
||||
{% include('header', { blank_page: true }) %}
|
||||
{% const boardinfo = ubus.call('system', 'board') ?? {}; -%}
|
||||
{% include('header', { blank_page: true, prefetched_boardinfo: boardinfo }) %}
|
||||
<style>:root{--login-bg-image:url('{{ media }}/images/login-bg.jpeg')}</style>
|
||||
|
||||
<main class="login-layout">
|
||||
@@ -24,9 +24,9 @@
|
||||
</div>
|
||||
|
||||
<form method="post" class="login-form">
|
||||
{% if (fuser): %}
|
||||
<div class="login-error animate-element animate-delay-200">
|
||||
{{ _('Invalid username and/or password! Please try again.') }}
|
||||
{% if (auth_message || fuser): %}
|
||||
<div class="login-error{% if (auth_plugin): %} warning{% endif %} animate-element animate-delay-200">
|
||||
{{ auth_message || _('Invalid username and/or password! Please try again.') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
id="luci_username"
|
||||
placeholder="{{ _('Username') }}"
|
||||
autocomplete="username"
|
||||
{% if (duser): %}value="{{ entityencode(duser, true) }}"{% else %}value=""{% endif %}
|
||||
value="{{ entityencode(duser ?? '', true) }}"
|
||||
{% if (!duser): %}autofocus{% endif %}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,7 +60,7 @@
|
||||
id="luci_password"
|
||||
placeholder="{{ _('Password') }}"
|
||||
autocomplete="current-password"
|
||||
autofocus
|
||||
{% if (duser): %}autofocus{% endif %}
|
||||
>
|
||||
<button type="button" class="login-pw-toggle" id="pw-toggle" aria-label="{{ _('Reveal/hide password') }}">
|
||||
<img src="{{ media }}/icons/eye.svg" id="pw-toggle-icon" width="16" height="16" alt="">
|
||||
@@ -67,6 +68,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (auth_fields): %}
|
||||
{% for (let field in auth_fields): %}
|
||||
<div class="login-field animate-element animate-delay-300">
|
||||
<label class="login-label" for="{{ field.name }}">{{ _(field.label ?? field.name) }}</label>
|
||||
<div class="login-input-glass">
|
||||
<input
|
||||
name="{{ field.name }}"
|
||||
id="{{ field.name }}"
|
||||
type="{{ field.type ?? 'text' }}"
|
||||
{% if (field.placeholder): %}placeholder="{{ field.placeholder }}"{% endif %}
|
||||
{% if (field.inputmode): %}inputmode="{{ field.inputmode }}"{% endif %}
|
||||
{% if (field.pattern): %}pattern="{{ field.pattern }}"{% endif %}
|
||||
{% if (field.maxlength): %}maxlength="{{ field.maxlength }}"{% endif %}
|
||||
{% if (field.autocomplete): %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if (field.required): %}required{% endif %}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if (auth_html): %}
|
||||
<div class="login-field animate-element animate-delay-300">
|
||||
{{ auth_html }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if (auth_assets): %}
|
||||
{% for (let asset in auth_assets): %}
|
||||
{% if (asset.type == 'script'): %}
|
||||
<script src="{{ asset.src }}"></script>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<input type="hidden" name="luci_magic" value="{{ luci_magic }}">
|
||||
|
||||
<button type="submit" class="login-submit animate-element animate-delay-400">
|
||||
|
||||
@@ -21,13 +21,13 @@ define Download/geoip
|
||||
HASH:=eedb24b3b9fcba27e06b92c0428dab90d9496083091ae4cf39e11387fd99d0e2
|
||||
endef
|
||||
|
||||
GEOSITE_VER:=20261103223032
|
||||
GEOSITE_VER:=20261104112839
|
||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||
define Download/geosite
|
||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||
URL_FILE:=dlc.dat
|
||||
FILE:=$(GEOSITE_FILE)
|
||||
HASH:=9c1b21ba6aef9d9a077642bf6bed52d736dce68eb46865bc9867f8eb73c4a410
|
||||
HASH:=72be4fe4e4045467b982808520051bb1d4af325af290e89999ffdea7aceec131
|
||||
endef
|
||||
|
||||
GEOSITE_IRAN_VER:=202610230802
|
||||
|
||||
Reference in new issue
Block a user