diff --git a/clashoo/Makefile b/clashoo/Makefile index 8b8087c8a..f7a40f9b5 100644 --- a/clashoo/Makefile +++ b/clashoo/Makefile @@ -1,17 +1,17 @@ include $(TOPDIR)/rules.mk PKG_NAME:=clashoo -PKG_SHORT_SHA:=63bd52e -PKG_COMMIT_DATE:=2026.09.27 +PKG_SHORT_SHA:=1341769 +PKG_COMMIT_DATE:=2026.09.30 PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA) -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_SOURCE:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA).tar.gz PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA) PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src -PKG_SOURCE_VERSION:=16a867c31778167b5297025cafd444ea8463b108 +PKG_SOURCE_VERSION:=0165638d50cfe2125ce47770bd75e2e007bbaffa PKG_HASH:=skip PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA) diff --git a/clashoo/files/usr/share/clashoo/runtime/mixin.uc b/clashoo/files/usr/share/clashoo/runtime/mixin.uc index 221a2fa9d..ddbf845fb 100644 --- a/clashoo/files/usr/share/clashoo/runtime/mixin.uc +++ b/clashoo/files/usr/share/clashoo/runtime/mixin.uc @@ -101,8 +101,25 @@ if (keep_fakeip) { delete cfg['dns']['fake-ip-filter']; } else { let push_filter = function(f) { - if (f == 'geosite:cn') { f = 'rule-set:cn_domain'; need_cn_rs = true; } - else if (f == 'rule-set:cn_domain') need_cn_rs = true; + f = trim(f || ''); + if (!length(f)) return; + if (filter_mode == 'rule') { + if (match(f, /,(fake-ip|real-ip)$/)) { push(cfg['dns']['fake-ip-filter'], f); return; } + if (index(f, ',') >= 0) { push(cfg['dns']['fake-ip-filter'], f + ',real-ip'); return; } + if (f == 'geosite:cn') { f = 'RULE-SET,cn_domain,real-ip'; need_cn_rs = true; } + else if (match(f, /^geosite:/)) { f = 'GEOSITE,' + substr(f, 8) + ',real-ip'; } + else if (match(f, /^rule-set:/)) { + let rs = substr(f, 9); + if (rs == 'cn_domain') need_cn_rs = true; + f = 'RULE-SET,' + rs + ',real-ip'; + } + else if (match(f, /^\+\./)) { f = 'DOMAIN-SUFFIX,' + substr(f, 2) + ',real-ip'; } + else if (match(f, /\*/)) { f = 'DOMAIN-WILDCARD,' + f + ',real-ip'; } + else { f = 'DOMAIN,' + f + ',real-ip'; } + } else { + if (f == 'geosite:cn') { f = 'rule-set:cn_domain'; need_cn_rs = true; } + else if (f == 'rule-set:cn_domain') need_cn_rs = true; + } push(cfg['dns']['fake-ip-filter'], f); }; let filters = a('fake_ip_filter'); diff --git a/geoview/Makefile b/geoview/Makefile index 3754d4e57..6fc545d34 100644 --- a/geoview/Makefile +++ b/geoview/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=geoview -PKG_VERSION:=0.2.6 -PKG_RELEASE:=5 +PKG_VERSION:=0.2.7 +PKG_RELEASE:=6 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/snowie2000/geoview/tar.gz/$(PKG_VERSION)? diff --git a/geth/Makefile b/geth/Makefile index 08d20b7ea..431647ac5 100644 --- a/geth/Makefile +++ b/geth/Makefile @@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=go-ethereum -PKG_VERSION:=1.17.6 -PKG_RELEASE:=10 +PKG_VERSION:=1.17.7 +PKG_RELEASE:=11 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/ethereum/go-ethereum/tar.gz/v${PKG_VERSION}? diff --git a/homeredirect/Makefile b/homeredirect/Makefile index f6dacfa69..b3a3ca11b 100644 --- a/homeredirect/Makefile +++ b/homeredirect/Makefile @@ -6,9 +6,9 @@ include $(TOPDIR)/rules.mk PKG_NAME:=HomeRedirect -PKG_VERSION:=1.4 -PKG_RELEASE:=1 -PKG_DATE:=20210226 +PKG_VERSION:=2.0 +PKG_RELEASE:=2 +PKG_DATE:=20260930 PKG_MAINTAINER:=xiaoqingfeng PKG_LICENSE:=GPL-3.0-or-later @@ -20,7 +20,7 @@ define Package/$(PKG_NAME) SECTION:=net CATEGORY:=Network TITLE:=Port forwarding utility for HomeLede. - DEPENDS:=+bash +coreutils-nohup +socat + DEPENDS:=+socat PKGARCH:=all URL:=https://github.com/xiaoqingfengATGH/feeds-xiaoqingfeng endef @@ -43,28 +43,44 @@ endef define Package/$(PKG_NAME)/install $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/config - $(INSTALL_DIR) $(1)/etc/homeredirect $(INSTALL_BIN) files/etc/init.d/homeredirect $(1)/etc/init.d $(INSTALL_CONF) files/etc/config/homeredirect $(1)/etc/config - $(INSTALL_DATA) files/etc/homeredirect/firewall.include $(1)/etc/homeredirect/ - $(INSTALL_DATA) files/etc/homeredirect/script.sh $(1)/etc/homeredirect/ endef define Package/$(PKG_NAME)/postinst #!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + rm -f /etc/homeredirect/firewall.include + rm -f /etc/homeredirect/script.sh + uci -q batch <<-EOF >/dev/null + delete firewall.homeredirect + delete ucitrack.@homeredirect[-1] + add ucitrack homeredirect + set ucitrack.@homeredirect[-1].init=homeredirect + commit ucitrack + commit firewall + EOF + rm -rf /tmp/luci-*cache +} exit 0 endef define Package/$(PKG_NAME)/prerm #!/bin/sh -/etc/init.d/homeredirect stop -uci -q batch <<-EOF >/dev/null - delete ucitrack.@homeredirect[-1] - commit ucitrack -EOF -uci -q batch <<-EOF >/dev/null - delete firewall.homeredirect -EOF +[ -n "$${IPKG_INSTROOT}" ] || { + /etc/init.d/homeredirect stop >/dev/null 2>&1 + uci -q batch <<-EOF >/dev/null + delete ucitrack.@homeredirect[-1] + delete firewall.homeredirect + commit ucitrack + EOF + # remove generated per-rule firewall rules (hr_ prefixed) + for s in $$(uci -q show firewall | sed -n "s/^firewall\.\(hr_[A-Za-z0-9_]*\)=rule$$/\1/p"); do + uci -q delete "firewall.$$s" + done + uci -q commit firewall + /etc/init.d/firewall reload >/dev/null 2>&1 +} exit 0 endef diff --git a/homeredirect/files/etc/config/homeredirect b/homeredirect/files/etc/config/homeredirect index 10afeb964..31a42dcc6 100644 --- a/homeredirect/files/etc/config/homeredirect +++ b/homeredirect/files/etc/config/homeredirect @@ -1,38 +1,13 @@ config global option enabled '1' - -config redirect - option proto 'tcp4' - option src_ip '0.0.0.0' - option src_dport '60609' - option dest_ip '192.168.1.100' - option dest_port '3389' - option name 'TCP_REDIRECT_IPV4' - option enabled '0' - -config redirect - option proto 'tcp6' - option src_ip '::' - option src_dport '60608' - option dest_ip 'fd5b:64cf:4ff4::1c4' - option dest_port '3389' - option name 'TCP_REDIRECT_IPV6' - option enabled '0' - -config redirect - option proto 'udp4' - option src_ip '0.0.0.0' - option src_dport '64511' - option dest_ip '192.168.1.100' - option dest_port '500' - option name 'UDP_REDIRECT_IPV4' - option enabled '0' + option cert '' + option key '' config redirect - option proto 'udp6' - option src_ip '::' - option src_dport '64500' - option dest_ip 'fd5b:64cf:4ff4::1c4' - option dest_port '4500' - option name 'UDP_REDIRECT_IPV6' - option enabled '0' \ No newline at end of file + option name 'demo-cross-family' + option proto 'tcp6' + option src_dport '60608' + option dest_ip '192.168.1.100' + option dest_port '3389' + option ipv6only '1' + option enabled '0' diff --git a/homeredirect/files/etc/homeredirect/firewall.include b/homeredirect/files/etc/homeredirect/firewall.include deleted file mode 100644 index 1356ac934..000000000 --- a/homeredirect/files/etc/homeredirect/firewall.include +++ /dev/null @@ -1 +0,0 @@ -bash /etc/homeredirect/script.sh \ No newline at end of file diff --git a/homeredirect/files/etc/homeredirect/script.sh b/homeredirect/files/etc/homeredirect/script.sh deleted file mode 100644 index f3707f739..000000000 --- a/homeredirect/files/etc/homeredirect/script.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -del_rule() { - count=$(iptables -n -L INPUT 2>/dev/null | grep -c "HOME_REDIRECT") - if [ -n "$count" ]; then - until [ "$count" = 0 ] - do - rules=$(iptables -n -L INPUT --line-num 2>/dev/null | grep "HOME_REDIRECT" | awk '{print $1}') - for rule in $rules - do - iptables -D INPUT $rule 2>/dev/null - break - done - count=$(expr $count - 1) - done - fi - - iptables -F HOME_REDIRECT 2>/dev/null - iptables -X HOME_REDIRECT 2>/dev/null -} - -add_rule(){ - iptables -N HOME_REDIRECT - iptables -I INPUT -j HOME_REDIRECT - - maxRedirctCount=$(uci show homeredirect | grep @redirect | awk -F '[' '{print $2}' | awk -F ']' '{print $1}' | sort | tail -n 1) - - for ((i=($maxRedirctCount);i>=0;i--)); - do - enabled=$(uci get homeredirect.@redirect[$i].enabled) - if [ $enabled -eq 1 ]; then - protoAll=$(uci get homeredirect.@redirect[$i].proto) - proto=${protoAll:0:3} - port=$(uci get homeredirect.@redirect[$i].src_dport) - iptables -A HOME_REDIRECT -p $proto --dport $port -j ACCEPT - fi - done -} - -del_rule - -enable=$(uci get homeredirect.@global[0].enabled) -if [ $enable -eq 1 ]; then - add_rule -fi diff --git a/homeredirect/files/etc/init.d/homeredirect b/homeredirect/files/etc/init.d/homeredirect index 61dcf1fdc..a4ca8f84f 100644 --- a/homeredirect/files/etc/init.d/homeredirect +++ b/homeredirect/files/etc/init.d/homeredirect @@ -1,140 +1,157 @@ #!/bin/sh /etc/rc.common +# HomeRedirect - CGNAT-era port forwarder (socat front-end, procd managed) +# Copyright 2020-2026 Richard +USE_PROCD=1 START=99 +STOP=10 RUNLOG_DIR=/tmp/hr +FW_PREFIX="hr_" -PROCESSED_REDIRECT=0 - -log() -{ - logger -t homeredirect $1 +log() { + logger -t homeredirect "$*" } -setupDefaultSrcIP() { - if [ -z $src_ip ];then - if [ "$1" = "ipv4" ]; then - src_ip="0.0.0.0" - else - src_ip="::" - fi - fi +# classify proto: addr_type (socat LISTEN address), transport, firewall +# proto/family, is_v6, is_tls +classify_proto() { + case "$1" in + tcp4) addr_type="TCP4-LISTEN"; transport="TCP"; fw_proto="tcp"; fw_family="ipv4"; is_v6=0; is_tls=0 ;; + udp4) addr_type="UDP4-LISTEN"; transport="UDP"; fw_proto="udp"; fw_family="ipv4"; is_v6=0; is_tls=0 ;; + tcp6) addr_type="TCP6-LISTEN"; transport="TCP"; fw_proto="tcp"; fw_family="ipv6"; is_v6=1; is_tls=0 ;; + udp6) addr_type="UDP6-LISTEN"; transport="UDP"; fw_proto="udp"; fw_family="ipv6"; is_v6=1; is_tls=0 ;; + tls4) addr_type="OPENSSL-LISTEN"; transport="TCP"; fw_proto="tcp"; fw_family="ipv4"; is_v6=0; is_tls=1 ;; + tls6) addr_type="OPENSSL-LISTEN"; transport="TCP"; fw_proto="tcp"; fw_family="ipv6"; is_v6=1; is_tls=1 ;; + *) return 1 ;; + esac } -setup() { +fw_add_rule() { + local id="$1" + local enabled proto src_dport - config_get enabled $1 enabled + config_get enabled "$id" enabled 0 + [ "$enabled" = "1" ] || return 0 + config_get proto "$id" proto + config_get src_dport "$id" src_dport + classify_proto "$proto" || return 0 - id=$1 - config_get proto $1 proto - config_get src_ip $1 src_ip - config_get src_dport $1 src_dport - config_get dest_ip $1 dest_ip - config_get dest_port $1 dest_port - config_get name $1 name - - terminateRedirect $id - - [ "$enabled" != "1" ] && return 0 - - PROCESSED_REDIRECT=1 - - if [ "$proto" = "tcp4" ]; then - src_addresstype="TCP4-LISTEN" - dest_addresstype="TCP4" - setupDefaultSrcIP "ipv4" - elif [ "$proto" = "tcp6" ]; then - src_addresstype="TCP6-LISTEN" - dest_addresstype="TCP6" - setupDefaultSrcIP "ipv6" - src_ip="[$src_ip]" - dest_ip="[$dest_ip]" - elif [ "$proto" = "udp4" ]; then - src_addresstype="UDP4-LISTEN" - dest_addresstype="UDP4" - setupDefaultSrcIP "ipv4" - elif [ "$proto" = "udp6" ]; then - src_addresstype="UDP6-LISTEN" - dest_addresstype="UDP6" - setupDefaultSrcIP "ipv6" - src_ip="[$src_ip]" - dest_ip="[$dest_ip]" - fi - - #echo "nohup socat -lf $RUNLOG_DIR/$id.log $src_addresstype:$src_dport,bind=$src_ip,fork $dest_addresstype:$dest_ip:$dest_port > $RUNLOG_DIR/$id.log 2>&1 &" - nohup socat -lf $RUNLOG_DIR/$id.log $src_addresstype:$src_dport,bind=$src_ip,fork $dest_addresstype:$dest_ip:$dest_port > $RUNLOG_DIR/$id.log 2>&1 & - log "[HomeRedirect] Port redirect from $proto $src_ip:$src_dport==>$dest_addresstype:$dest_ip:$dest_port started." + uci -q set "firewall.${FW_PREFIX}${id}=rule" + uci -q set "firewall.${FW_PREFIX}${id}.target=ACCEPT" + uci -q set "firewall.${FW_PREFIX}${id}.src=wan" + uci -q set "firewall.${FW_PREFIX}${id}.proto=$fw_proto" + uci -q set "firewall.${FW_PREFIX}${id}.dest_port=$src_dport" + uci -q set "firewall.${FW_PREFIX}${id}.family=$fw_family" + uci -q set "firewall.${FW_PREFIX}${id}.name=homeredirect: $src_dport/$fw_proto ($fw_family)" } -# param $1 is port -showTcpPortState() { - local process=$(netstat -ltnp | awk -F ' ' '{if(NR>2) print $1"/"$4"/"$7}' | grep :$1) - if [ -n "$process" ]; then - echo $process - else - echo 'TCP Port $1 is Free.' - fi -} -# param $1 is port -showUdpPortState() { - local process=$(netstat -lunp | awk -F ' ' '{if(NR>2) print $1"/"$4"/"$6}'|grep :$1) - if [ -n "$process" ]; then - echo $process - else - echo 'UDP Port $1 is Free.' - fi +# regenerate the UCI firewall rules owned by this service (fw3 and fw4 both +# render "config rule" with src=wan into the wan INPUT chain); replaces the +# old script.sh that wrote iptables rules directly +sync_firewall() { + local s changed=0 + + for s in $(uci -q show firewall 2>/dev/null | sed -n "s/^firewall\.\(${FW_PREFIX}[A-Za-z0-9_]*\)=rule$/\1/p"); do + uci -q delete "firewall.$s" + changed=1 + done + + [ "$1" = "add" ] && { + config_foreach fw_add_rule redirect + changed=1 + } + + [ "$changed" = "1" ] && { + uci -q commit firewall + /etc/init.d/firewall reload >/dev/null 2>&1 + } } -isRedirectRunning() { - local runningPID=$(ps | grep socat | grep $RUNLOG_DIR/$1 | sed '/grep/d' | awk -F ' ' '{print $1}') - if [ -n "$runningPID" ]; then - return 1 - else +setup_instance() { + local id="$1" + local enabled proto src_ip src_dport dest_ip dest_port name ipv6only + local cert key listen_opts listen_addr + + config_get enabled "$id" enabled 0 + config_get proto "$id" proto + config_get src_ip "$id" src_ip + config_get src_dport "$id" src_dport + config_get dest_ip "$id" dest_ip + config_get dest_port "$id" dest_port + config_get name "$id" name + config_get ipv6only "$id" ipv6only 1 + + [ "$enabled" = "1" ] || return 0 + + classify_proto "$proto" || { + log "rule $name: unknown proto '$proto' - skipping" return 0 - fi -} - -# param $1 is redirect id -terminateRedirect() { - isRedirectRunning $1 - [ "$?" = "1" ] && { - local runningPID=$(ps | grep socat | grep $RUNLOG_DIR/$1 | sed '/grep/d' | awk -F ' ' '{print $1}') - #echo "Going to kill process $runningPID" - kill $runningPID } -} -terminateAll() { - local runningPIDs=$(ps | grep socat | grep $RUNLOG_DIR | sed '/grep/d' | awk -F ' ' '{print $1}') - [ -n "$runningPIDs" ] && { - kill $runningPIDs - log "Redirect process : $runningPIDs stopped." - } -} + # listener bind address defaults by family + [ -z "$src_ip" ] && { [ "$is_v6" = "1" ] && src_ip="::" || src_ip="0.0.0.0"; } -start() { - local vt_enabled=$(uci -q get homeredirect.@global[0].enabled) - if [ "$vt_enabled" = 0 ]; then - terminateAll - fw3 reload - return 1 + # destination side is family neutral: bare v6 literals get bracketed, + # v4 literals and hostnames pass through, hostnames are re-resolved by + # socat on every forked connection + case "$dest_ip" in + \[*) ;; # already bracketed + *:*) dest_ip="[$dest_ip]" ;; # bare v6 literal + esac + + listen_opts="fork,reuseaddr" + # socat >= 1.8.1 resolves bind= with AF_INET unless pf=ip6 is given + [ "$is_v6" = "1" ] && listen_opts="$listen_opts,pf=ip6" + # v6 listeners default to ipv6only: pure v6 entry in a CGNAT world, + # v4-mapped connections are refused unless the rule opts out + [ "$is_v6" = "1" ] && [ "$ipv6only" = "1" ] && listen_opts="$listen_opts,ipv6only=1" + + listen_addr="$addr_type" + if [ "$is_tls" = "1" ]; then + cert=$(uci -q get homeredirect.@global[0].cert) + key=$(uci -q get homeredirect.@global[0].key) + if [ -z "$cert" ] || [ -z "$key" ] || [ ! -f "$cert" ] || [ ! -f "$key" ]; then + log "rule $name: TLS listener needs global cert/key - skipping" + return 0 + fi + listen_opts="$listen_opts,cert=$cert,key=$key,verify=0" fi - - rm -rf $RUNLOG_DIR - mkdir -p $RUNLOG_DIR - + + log "rule $name: $proto $src_ip:$src_dport -> $transport:$dest_ip:$dest_port" + + procd_open_instance "$id" + procd_set_param command /usr/bin/socat -lf "$RUNLOG_DIR/$id.log" \ + "$listen_addr:$src_dport,bind=$src_ip,$listen_opts" \ + "$transport:$dest_ip:$dest_port" + procd_set_param respawn 3600 5 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +start_service() { + mkdir -p "$RUNLOG_DIR" + + [ "$(uci -q get homeredirect.@global[0].enabled)" = "1" ] || { + sync_firewall + return 0 + } + config_load homeredirect - - PROCESSED_REDIRECT=0 - config_foreach setup redirect - [ "$PROCESSED_REDIRECT" == "1" ] && { - fw3 reload - } - log 'HomeRedirect started.' + config_foreach setup_instance redirect + sync_firewall add } -stop() { - terminateAll - fw3 reload - log 'HomeRedirect stopped.' +stop_service() { + sync_firewall + rm -rf "$RUNLOG_DIR" +} + +service_triggers() { + procd_add_reload_trigger "homeredirect" +} + +reload_service() { + restart } diff --git a/linkease-common-bin/Makefile b/linkease-common-bin/Makefile index df5964e7e..7f79e4728 100644 --- a/linkease-common-bin/Makefile +++ b/linkease-common-bin/Makefile @@ -11,8 +11,8 @@ PKG_ARCH_LINKEASE:=$(ARCH) PKG_NAME:=linkease-common-bin # use PKG_SOURCE_DATE instead of PKG_VERSION for compitable -PKG_SOURCE_DATE:=1.7.5 -PKG_RELEASE:=42 +PKG_SOURCE_DATE:=1.7.6 +PKG_RELEASE:=43 ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) @@ -67,3 +67,12 @@ define Package/$(PKG_NAME)/install endef $(eval $(call BuildPackage,$(PKG_NAME))) + +# OpenWrt 24.10 does not expose a package-level REPLACES variable in +# package-pack.mk. Preserve the generated control stanza and append the +# versioned ownership hand-off required for legacy monolithic LinkEase packages. +LINKEASE_COMMON_BIN_GENERATED_CONTROL:=$(Package/$(PKG_NAME)/CONTROL) +define Package/$(PKG_NAME)/CONTROL +$(LINKEASE_COMMON_BIN_GENERATED_CONTROL) +Replaces: linkease (<< 1.7.6~) +endef diff --git a/linkease-runtime-transition/Makefile b/linkease-runtime-transition/Makefile new file mode 100644 index 000000000..2edd4c6bc --- /dev/null +++ b/linkease-runtime-transition/Makefile @@ -0,0 +1,36 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=linkease-runtime-transition +PKG_VERSION:=1.7.6 +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=net + CATEGORY:=Network + SUBMENU:=Web Servers/Proxies + TITLE:=LinkEase split-runtime transition contract + DEPENDS:=@(arm||x86_64||aarch64) +linkease +luci-lib-linkeasefile + EXTRA_DEPENDS:=linkease (>= 1.7.6~), luci-lib-linkeasefile (>= 2.1.70-r4) + PKGARCH:=all + URL:=https://www.linkease.com/ +endef + +define Package/$(PKG_NAME)/description + Forces legacy LinkEase installations onto the split-safe runtime and LuCI + ownership generation without exposing version expressions to app-meta hooks. +endef + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/share/linkease + $(INSTALL_DATA) ./files/runtime-generation $(1)/usr/share/linkease/runtime-generation +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/linkease-runtime-transition/files/runtime-generation b/linkease-runtime-transition/files/runtime-generation new file mode 100644 index 000000000..de28578af --- /dev/null +++ b/linkease-runtime-transition/files/runtime-generation @@ -0,0 +1 @@ +1.7.6 diff --git a/linkease/Makefile b/linkease/Makefile index f06319c7d..6ed74d914 100644 --- a/linkease/Makefile +++ b/linkease/Makefile @@ -11,8 +11,8 @@ PKG_ARCH_LINKEASE:=$(ARCH) PKG_NAME:=linkease # use PKG_SOURCE_DATE instead of PKG_VERSION for compitable -PKG_SOURCE_DATE:=1.7.5 -PKG_RELEASE:=45 +PKG_SOURCE_DATE:=1.7.6 +PKG_RELEASE:=46 ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) @@ -44,6 +44,7 @@ define Package/$(PKG_NAME) SUBMENU:=Web Servers/Proxies TITLE:=LinkEase - the file cloud DEPENDS:=@(arm||x86_64||aarch64) +linkease-common-bin + EXTRA_DEPENDS:=linkease-common-bin (>= 1.7.6~) PKGARCH:=all URL:=https://www.linkease.com/ endef diff --git a/linkeasefull/Makefile b/linkeasefull/Makefile index 2ee158d4f..2a38a16aa 100644 --- a/linkeasefull/Makefile +++ b/linkeasefull/Makefile @@ -11,7 +11,7 @@ PKG_ARCH_LINKEASE:=$(ARCH) PKG_NAME:=linkeasefull # use PKG_SOURCE_DATE instead of PKG_VERSION for compitable PKG_SOURCE_DATE:=3.0.22 -PKG_RELEASE:=44 +PKG_RELEASE:=45 ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) @@ -40,6 +40,7 @@ define Package/$(PKG_NAME) SUBMENU:=Web Servers/Proxies TITLE:=LinkEase Full - the local web desktop DEPENDS:=@(x86_64||aarch64) +linkease-app-entry +linkease-common-bin +ca-bundle +cifsmount +kmod-fs-cifs + EXTRA_DEPENDS:=linkease-common-bin (>= 1.7.6~) PKGARCH:=all URL:=https://www.linkease.com/ endef diff --git a/luci-app-adguardhome/.official_channel b/luci-app-adguardhome/.official_channel index d7025695e..8ab67f2e7 100644 --- a/luci-app-adguardhome/.official_channel +++ b/luci-app-adguardhome/.official_channel @@ -1 +1 @@ -release +snapshot diff --git a/luci-app-adguardhome/.official_json_version b/luci-app-adguardhome/.official_json_version index 9b927c404..8db3474b4 100644 --- a/luci-app-adguardhome/.official_json_version +++ b/luci-app-adguardhome/.official_json_version @@ -1 +1 @@ -26.270.72870~a24d1f2 +26.272.65915~80de46c diff --git a/luci-app-airoha-npu/Makefile b/luci-app-airoha-npu/Makefile index 84e877601..891cdead0 100644 --- a/luci-app-airoha-npu/Makefile +++ b/luci-app-airoha-npu/Makefile @@ -3,7 +3,7 @@ include $(TOPDIR)/rules.mk PKG_VERSION:=1.0.1 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_LICENSE:=Apache-2.0 LUCI_TITLE:=LuCI Airoha SoC Status (NPU, CPU, Frame Engine) diff --git a/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js b/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js index 3944f7cd7..b7327465c 100644 --- a/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js +++ b/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js @@ -71,14 +71,14 @@ var bandInfo = [ var psePortMap = [ { name: 'CDM1', label: 'CPU DMA 1', color: '#607d8b' }, - { name: 'GDM1', label: 'Switch 1G', color: '#ff9800' }, + { name: 'GDM1', label: _('Switch 1G'), color: '#ff9800' }, { name: 'GDM2', label: 'WAN 10G', color: '#4caf50' }, { name: 'GDM3', label: 'GDM3', color: '#607d8b' }, - { name: 'PPE1', label: 'PPE Eng 1', color: '#2196f3' }, + { name: 'PPE1', label: _('PPE Eng 1'), color: '#2196f3' }, { name: 'CDM2', label: 'CPU DMA 2', color: '#607d8b' }, { name: 'CDM3', label: 'CDM3', color: '#607d8b' }, { name: 'CDM4', label: 'WDMA WiFi', color: '#9c27b0' }, - { name: 'PPE2', label: 'PPE Eng 2', color: '#2196f3' }, + { name: 'PPE2', label: _('PPE Eng 2'), color: '#2196f3' }, { name: 'GDM4', label: 'LAN2 10G', color: '#4caf50' } ]; @@ -102,7 +102,7 @@ function calcTotalMem(regions) { function tokenHealth(c, s) { if (!s) return { text: 'N/A', color: '#888' }; var p = c/s*100; - return p < 50 ? { text:'Healthy', color:'#4caf50' } : p < 80 ? { text:'Warning', color:'#ff9800' } : { text:'Critical', color:'#f44336' }; + return p < 50 ? { text:_('Healthy'), color:'#4caf50' } : p < 80 ? { text:_('Warning'), color:'#ff9800' } : { text:_('Critical'), color:'#f44336' }; } function getBandStats(ti, b) { @@ -118,10 +118,10 @@ function getTxQueue(ti, b) { } function bandHealth(s) { - if (!s || s.count===0) return { text:'No clients', color:'#888' }; - if (!s.tx_packets) return { text:'Idle', color:'#888' }; + if (!s || s.count===0) return { text:_('No clients'), color:'#888' }; + if (!s.tx_packets) return { text:_('Idle'), color:'#888' }; var r = s.tx_retries/(s.tx_packets+s.tx_retries); - return r>0.5 ? {text:'Poor',color:'#f44336'} : r>0.2 ? {text:'Fair',color:'#ff9800'} : {text:'Good',color:'#4caf50'}; + return r>0.5 ? {text:_('Poor'),color:'#f44336'} : r>0.2 ? {text:_('Fair'),color:'#ff9800'} : {text:_('Good'),color:'#4caf50'}; } function retryPct(s) { @@ -131,7 +131,7 @@ function retryPct(s) { /* ── Mini Band Chip (compact for FE diagram) ── */ function renderBandChip(band, txQ, stats) { - var info = bandInfo[band] || { name: 'Band '+band, accent: '#888' }; + var info = bandInfo[band] || { name: _('Band %d').format(band), accent: '#888' }; var id = 'band-'+band; var h = bandHealth(stats); var type = txQ ? txQ.type : '?'; @@ -147,7 +147,7 @@ function renderBandChip(band, txQ, stats) { E('span', { 'style': 'width:7px;height:7px;border-radius:50%;background:'+h.color+';display:inline-block' }), E('span', { 'style': 'color:'+h.color+';font-weight:500' }, h.text) ]), - E('span', { 'id': id+'-clients', 'class': 'soc-muted' }, stats.count + ' sta'), + E('span', { 'id': id+'-clients', 'class': 'soc-muted' }, _('%d clients').format(stats.count)), (stats.tx_packets > 0) ? E('span', { 'id': id+'-retries', 'class': 'soc-muted' }, rp) : E('span') ]) ]); @@ -158,14 +158,14 @@ function updateBandChip(band, stats) { var el = document.getElementById(id+'-health'); if (el) { el.innerHTML = ''; el.appendChild(E('span',{'style':'width:6px;height:6px;border-radius:50%;background:'+h.color+';display:inline-block'})); el.appendChild(E('span',{'style':'color:'+h.color+';font-weight:500;font-size:11px'},h.text)); } var cl = document.getElementById(id+'-clients'); - if (cl) cl.textContent = stats.count+'sta'; + if (cl) cl.textContent = _('%d clients').format(stats.count); var re = document.getElementById(id+'-retries'); if (re) { var rp2 = retryPct(stats); re.textContent = rp2; } } /* ── Frame Engine Diagram (with WiFi bands, NPU, PPE flows) ── */ function renderFeDiagram(fe, ti, st) { - if (!fe || fe.error) return E('div', { 'class': 'soc-muted' }, 'devmem not available on this build'); + if (!fe || fe.error) return E('div', { 'class': 'soc-muted' }, _('devmem not available on this build')); ti = ti || {}; st = st || {}; var ports = Array.isArray(fe.pse_ports) ? fe.pse_ports : []; @@ -184,9 +184,9 @@ function renderFeDiagram(fe, ti, st) { E('span', { 'class': 'soc-muted' }, 'TX'), E('span', { 'class': 'soc-text', 'style': 'text-align:right' }, fmtK(d.tx)), E('span', { 'class': 'soc-muted' }, 'RX'), E('span', { 'class': 'soc-text', 'style': 'text-align:right' }, fmtK(d.rx)) ].concat(d.tx_drop > 0 ? [ - E('span', { 'style': 'color:#f44336' }, 'TX Drop'), E('span', { 'style': 'color:#f44336;text-align:right' }, fmtK(d.tx_drop)) + E('span', { 'style': 'color:#f44336' }, _('TX Drop')), E('span', { 'style': 'color:#f44336;text-align:right' }, fmtK(d.tx_drop)) ] : []).concat(d.rx_drop > 0 ? [ - E('span', { 'style': 'color:#f44336' }, 'RX Drop'), E('span', { 'style': 'color:#f44336;text-align:right' }, fmtK(d.rx_drop)) + E('span', { 'style': 'color:#f44336' }, _('RX Drop')), E('span', { 'style': 'color:#f44336;text-align:right' }, fmtK(d.rx_drop)) ] : [])) ]); } @@ -202,7 +202,7 @@ function renderFeDiagram(fe, ti, st) { E('span', { 'style': 'font-weight:bold;color:#607d8b;font-size:13px' }, name+' '+pse), E('span', { 'class': 'soc-label' }, label) ]), - E('div', { 'class': 'soc-text', 'style': 'font-size:12px;margin-bottom:4px' }, 'HW Offload: '+pct+'%'), + E('div', { 'class': 'soc-text', 'style': 'font-size:12px;margin-bottom:4px' }, _('HW Offload:')+' '+pct+'%'), E('div', { 'class': 'soc-bar-track', 'style': 'height:6px' }, [ E('div', { 'style': 'background:'+barCol+';height:100%;width:'+pct+'%;transition:width .5s;border-radius:4px' }) ]), @@ -228,7 +228,7 @@ function renderFeDiagram(fe, ti, st) { E('div', { 'style': 'display:flex;gap:12px;font-size:11px;margin-bottom:8px' }, [ E('span', { 'class': 'soc-muted' }, 'IQ '+p7.iq), E('span', { 'class': 'soc-muted' }, 'OQ '+p7.oq), - p7.drops > 0 ? E('span', { 'style': 'color:#f44336' }, 'Drop '+fmtK(p7.drops)) : null + p7.drops > 0 ? E('span', { 'style': 'color:#f44336' }, _('Drop')+' '+fmtK(p7.drops)) : null ].filter(Boolean)), // WiFi bands inside E('div', { 'style': 'display:grid;grid-template-columns:repeat(3,1fr);gap:6px' }, bandChips) @@ -239,28 +239,28 @@ function renderFeDiagram(fe, ti, st) { var npuCard = E('div', { 'class': 'soc-card', 'style': 'border-color:'+(npuActive?'#00bcd4':'var(--soc-border)') }, [ E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;margin-bottom:4px' }, [ E('span', { 'style': 'font-weight:bold;color:#00bcd4;font-size:14px' }, 'NPU'), - E('span', { 'style': 'background:'+(npuActive?'#00695c':'#666')+';color:#fff;padding:1px 7px;border-radius:3px;font-size:10px;font-weight:600' }, npuActive ? 'ACTIVE' : 'OFF') + E('span', { 'style': 'background:'+(npuActive?'#00695c':'#666')+';color:#fff;padding:1px 7px;border-radius:3px;font-size:10px;font-weight:600' }, npuActive ? _('ACTIVE') : _('OFF')) ]), - E('div', { 'class': 'soc-label', 'style': 'margin-bottom:4px' }, '8x RISC-V via PCIe RAM'), + E('div', { 'class': 'soc-label', 'style': 'margin-bottom:4px' }, _('8x RISC-V via PCIe RAM')), E('div', { 'style': 'font-size:11px' }, [ - E('span', { 'class': 'soc-muted' }, 'Manages: '), - E('span', { 'class': 'soc-text', 'style': 'font-size:11px' }, 'PPE init, WDMA rings, flow stats') + E('span', { 'class': 'soc-muted' }, _('Manages:')+' '), + E('span', { 'class': 'soc-text', 'style': 'font-size:11px' }, _('PPE init, WDMA rings, flow stats')) ]) ]); // PPE engines with flow count var ppeCard = E('div', { 'class': 'soc-card', 'style': 'border-color:#2196f3' }, [ E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;margin-bottom:4px' }, [ - E('span', { 'style': 'font-weight:bold;color:#2196f3;font-size:14px' }, 'PPE Engines'), + E('span', { 'style': 'font-weight:bold;color:#2196f3;font-size:14px' }, _('PPE Engines')), E('span', { 'class': 'soc-label' }, 'P4 + P8') ]), E('div', { 'style': 'display:flex;gap:16px;font-size:12px' }, [ E('span', {}, [ - E('span', { 'class': 'soc-muted' }, 'Bound '), + E('span', { 'class': 'soc-muted' }, _('Bound')+' '), E('span', { 'class': 'soc-text', 'style': 'font-weight:bold', 'id': 'fe-ppe-bound' }, (st.offload_bound||0).toString()) ]), E('span', {}, [ - E('span', { 'class': 'soc-muted' }, 'Total '), + E('span', { 'class': 'soc-muted' }, _('Total')+' '), E('span', { 'class': 'soc-text', 'id': 'fe-ppe-total' }, (st.offload_total||0).toString()) ]) ]) @@ -289,8 +289,8 @@ function renderFeDiagram(fe, ti, st) { // PSE buffer bar E('div', { 'class': 'soc-card', 'style': 'margin-bottom:10px' }, [ E('div', { 'style': 'display:flex;justify-content:space-between;margin-bottom:4px' }, [ - E('span', { 'class': 'soc-text', 'style': 'font-weight:bold;font-size:13px' }, 'PSE Shared Buffer'), - E('span', { 'class': 'soc-muted', 'style': 'font-size:12px' }, (fe.pse_used||0)+' used / '+(fe.pse_free||0)+' free ('+pseP+'%)') + E('span', { 'class': 'soc-text', 'style': 'font-weight:bold;font-size:13px' }, _('PSE Shared Buffer')), + E('span', { 'class': 'soc-muted', 'style': 'font-size:12px' }, _('%d used / %d free (%s%%)').format(fe.pse_used||0, fe.pse_free||0, pseP)) ]), E('div', { 'class': 'soc-bar-track', 'style': 'height:8px' }, [ E('div', { 'style': 'background:'+pseCol+';height:100%;width:'+pseP+'%;border-radius:4px;transition:width .5s' }) @@ -298,7 +298,7 @@ function renderFeDiagram(fe, ti, st) { ]), // Row 1: GDM ports E('div', { 'class': 'soc-gdm-grid' }, [ - gdmCard('gdm1', 'GDM1', 'Internal Switch (1G LAN3/4)', '#ff9800', 'P1'), + gdmCard('gdm1', 'GDM1', _('Internal Switch (1G LAN3/4)'), '#ff9800', 'P1'), gdmCard('gdm2', 'GDM2', 'WAN (USXGMII 10G)', '#4caf50', 'P2'), gdmCard('gdm4', 'GDM4', 'LAN2 (USXGMII 10G)', '#4caf50', 'P9') ]), @@ -314,7 +314,7 @@ function renderFeDiagram(fe, ti, st) { npuCard ]), // PSE port grid - E('div', { 'class': 'soc-text', 'style': 'font-size:12px;font-weight:600;margin-bottom:6px' }, 'PSE Port Queue Status'), + E('div', { 'class': 'soc-text', 'style': 'font-size:12px;font-weight:600;margin-bottom:6px' }, _('PSE Port Queue Status')), E('div', { 'class': 'soc-pse-grid' }, portCells) ]); } @@ -331,7 +331,7 @@ function renderFreqBar(hw, min, max, pll, gov) { var pct = Math.round(((s.freq-min)/(s.max-min))*100); pct = Math.max(0,Math.min(100,pct)); var bg = s.oc ? 'linear-gradient(90deg,#e65100,#ff9800)' : 'linear-gradient(90deg,#2e7d32,#66bb6a)'; - var label = s.oc ? (pll+' MHz (OC)') : fmtFreq(s.freq); + var label = s.oc ? (_('%s MHz (OC)').format(pll)) : fmtFreq(s.freq); return E('div', { 'id':'cpu-freq-bar-wrap', 'style':'display:flex;align-items:center;gap:10px' }, [ E('span', { 'class':'soc-muted', 'style':'font-size:90%' }, fmtFreq(min)), @@ -346,7 +346,7 @@ function renderFreqBar(hw, min, max, pll, gov) { function updateFreqBar(hw, min, max, pll, gov) { var s = freqBarState(hw,min,max,pll,gov); var el = document.getElementById('cpu-freq-text'), fl = document.getElementById('cpu-freq-fill'), ml = document.getElementById('cpu-freq-max-label'); - if (el) el.textContent = s.oc ? (pll+' MHz (OC)') : fmtFreq(s.freq); + if (el) el.textContent = s.oc ? (_('%s MHz (OC)').format(pll)) : fmtFreq(s.freq); if (fl && s.max>0) { var pct=Math.max(0,Math.min(100,Math.round(((s.freq-min)/(s.max-min))*100))); fl.style.width=pct+'%'; fl.style.background=s.oc?'linear-gradient(90deg,#e65100,#ff9800)':'linear-gradient(90deg,#2e7d32,#66bb6a)'; } if (ml) ml.textContent = fmtFreq(s.max); } @@ -356,7 +356,7 @@ function renderGovSelect(avail, active) { if (!gs.length) return E('span',{},'N/A'); return E('select', { 'id':'cpu-governor-select','class':'cbi-input-select','style':'min-width:140px','change':function(ev){ var g=ev.target.value; ev.target.disabled=true; - callSetGovernor(g).then(function(r){ev.target.disabled=false;if(r&&r.error) ui.addNotification(null,E('p',{},_('Error: ')+r.error),'error');}).catch(function(){ev.target.disabled=false;}); + callSetGovernor(g).then(function(r){ev.target.disabled=false;if(r&&r.error) ui.addNotification(null,E('p',{},_('Error:')+' '+r.error),'error');}).catch(function(){ev.target.disabled=false;}); }}, gs.map(function(g){return E('option',{'value':g,'selected':g===active?'':null},g);})); } @@ -365,7 +365,7 @@ function renderMaxFreqSelect(avail, cur) { if (!fs.length) return E('span',{},'N/A'); return E('select', { 'id':'cpu-maxfreq-select','class':'cbi-input-select','style':'min-width:140px','change':function(ev){ var f=ev.target.value; ev.target.disabled=true; - callSetMaxFreq(parseInt(f)).then(function(r){ev.target.disabled=false;if(r&&r.error) ui.addNotification(null,E('p',{},_('Error: ')+r.error),'error');}).catch(function(){ev.target.disabled=false;}); + callSetMaxFreq(parseInt(f)).then(function(r){ev.target.disabled=false;if(r&&r.error) ui.addNotification(null,E('p',{},_('Error:')+' '+r.error),'error');}).catch(function(){ev.target.disabled=false;}); }}, fs.map(function(f){return E('option',{'value':f,'selected':parseInt(f)===parseInt(cur)?'':null},(parseInt(f)/1000).toFixed(0)+' MHz');})); } @@ -374,11 +374,11 @@ function renderOcControls() { var btn = E('button',{'class':'cbi-button cbi-button-action','style':'margin-left:8px','click':function(){ var f=parseInt(document.getElementById('oc-freq-input').value); if(isNaN(f)||f<500||f>1600){ui.addNotification(null,E('p',{},_('Must be 500-1600 MHz')),'error');return;} - if(f>1400&&!confirm('Frequencies above 1400 MHz may be unstable. Continue?')) return; + if(f>1400&&!confirm(_('Frequencies above 1400 MHz may be unstable. Continue?'))) return; btn.disabled=true;btn.textContent=_('Applying...'); callSetOverclock(f).then(function(r){btn.disabled=false;btn.textContent=_('Apply'); - if(r&&r.error) ui.addNotification(null,E('p',{},_('Failed: ')+r.error),'error'); - else if(r&&r.result==='ok') ui.addNotification(null,E('p',{},_('CPU set to ')+r.actual_mhz+' MHz'),'info'); + if(r&&r.error) ui.addNotification(null,E('p',{},_('Failed:')+' '+r.error),'error'); + else if(r&&r.result==='ok') ui.addNotification(null,E('p',{},_('CPU set to')+' '+r.actual_mhz+' MHz'),'info'); }).catch(function(e){btn.disabled=false;btn.textContent=_('Apply');}); }},_('Apply')); return E('div',{'style':'display:flex;align-items:center;gap:8px;flex-wrap:wrap'},[ @@ -434,9 +434,9 @@ return view.extend({ E('span',{'class':'label-success'},_('Active')+(st.npu_device?' ('+st.npu_device+')':'')) : E('span',{'class':'label-danger'},_('Not Active'))) ]), E('tr',{'class':'tr'},[ E('td',{'class':'td'},E('strong',{},_('Firmware / Clock / Cores'))), - E('td',{'class':'td','id':'npu-info'}, (st.npu_version||'N/A')+' | '+(st.npu_clock?(st.npu_clock/1e6).toFixed(0)+' MHz':'N/A')+' | '+(st.npu_cores||0)+' cores') ]), + E('td',{'class':'td','id':'npu-info'}, (st.npu_version||'N/A')+' | '+(st.npu_clock?(st.npu_clock/1e6).toFixed(0)+' MHz':'N/A')+' | '+_('%d cores').format(st.npu_cores||0)) ]), E('tr',{'class':'tr'},[ E('td',{'class':'td'},E('strong',{},_('Reserved Memory'))), - E('td',{'class':'td','id':'npu-memory'}, calcTotalMem(memR)+' ('+memR.length+' regions)') ]) + E('td',{'class':'td','id':'npu-memory'}, _('%s (%d regions)').format(calcTotalMem(memR), memR.length)) ]) ]), // Frame Engine diagram (includes WiFi bands, PPE flows, NPU indicator) diff --git a/luci-app-airoha-npu/po/templates/luci-app-airoha-npu.pot b/luci-app-airoha-npu/po/templates/luci-app-airoha-npu.pot index 6ac192e98..d51fb3965 100644 --- a/luci-app-airoha-npu/po/templates/luci-app-airoha-npu.pot +++ b/luci-app-airoha-npu/po/templates/luci-app-airoha-npu.pot @@ -1,73 +1,125 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:327 -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:406 -msgid "Active" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:150 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:161 +msgid "%d clients" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:284 -msgid "Airoha SoC Status" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:437 +msgid "%d cores" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:224 -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:233 -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:237 -msgid "Apply" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:293 +msgid "%d used / %d free (%s%%)" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:221 -msgid "Applying..." +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:439 +msgid "%s (%d regions)" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:350 -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:420 -msgid "Bound:" -msgstr "" - -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:362 -msgid "Bytes" -msgstr "" - -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:310 -msgid "CPU Cores" -msgstr "" - -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:288 -msgid "CPU Frequency" -msgstr "" - -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:229 -msgid "CPU set to" -msgstr "" - -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:291 -msgid "Current Frequency" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:334 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:349 +msgid "%s MHz (OC)" msgstr "" #: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:244 -msgid "" -"Direct PLL programming. Governor locked to performance. Stock max: 1200 MHz. " -"Tested stable up to 1500 MHz." +msgid "8x RISC-V via PCIe RAM" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:360 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:242 +msgid "ACTIVE" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:434 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:470 +msgid "Active" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:414 +msgid "Airoha SoC Status" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:379 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:382 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:383 +msgid "Apply" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:378 +msgid "Applying..." +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:134 +msgid "Band %d" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:259 +msgid "Bound" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:424 +msgid "CPU Cores" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:418 +msgid "CPU Frequency" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:381 +msgid "CPU set to" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:105 +msgid "Critical" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:420 +msgid "Current Frequency" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:386 +msgid "Direct PLL. Stock max 1200 MHz. Stable up to 1500 MHz." +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:231 +msgid "Drop" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:359 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:368 +msgid "Error:" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:453 msgid "Ethernet" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:85 -msgid "Failed to set governor:" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:380 +msgid "Failed:" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:112 -msgid "Failed to set max frequency:" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:124 +msgid "Fair" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:212 -msgid "Frequency must be 500-1600 MHz" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:436 +msgid "Firmware / Clock / Cores" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:296 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:443 +msgid "Frame Engine" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:377 +msgid "Frequencies above 1400 MHz may be unstable. Continue?" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:124 +msgid "Good" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:421 msgid "Governor" msgstr "" @@ -75,69 +127,108 @@ msgstr "" msgid "Grant access to Airoha SoC status (NPU + CPU + Overclock)" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:355 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:205 +msgid "HW Offload:" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:105 +msgid "Healthy" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:122 +msgid "Idle" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:452 msgid "Index" msgstr "" #: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:301 +msgid "Internal Switch (1G LAN3/4)" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:246 +msgid "Manages:" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:422 msgid "Max Frequency" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:331 -msgid "NPU Clock / Cores" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:376 +msgid "Must be 500-1600 MHz" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:321 -msgid "NPU Firmware Version" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:430 +msgid "NPU & Offload Engine" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:318 -msgid "NPU Information" -msgstr "" - -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:325 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:432 msgid "NPU Status" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:359 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:453 msgid "New Flow" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:328 -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:411 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:121 +msgid "No clients" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:435 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:470 msgid "Not Active" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:322 -msgid "Not available" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:242 +msgid "OFF" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:339 -msgid "Offload Statistics" -msgstr "" - -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:358 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:453 msgid "Original Flow" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:306 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:423 msgid "Overclock" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:226 -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:234 -msgid "Overclock failed:" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:77 +msgid "PPE Eng 1" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:347 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:81 +msgid "PPE Eng 2" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:254 +msgid "PPE Engines" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:449 msgid "PPE Flow Offload Entries" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:361 -msgid "Packets" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:247 +msgid "PPE init, WDMA rings, flow stats" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:335 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:317 +msgid "PSE Port Queue Status" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:292 +msgid "PSE Shared Buffer" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:124 +msgid "Poor" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:189 +msgid "RX Drop" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:438 msgid "Reserved Memory" msgstr "" @@ -145,20 +236,30 @@ msgstr "" msgid "SoC Status" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:356 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:452 msgid "State" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:349 -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:419 -msgid "Total:" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:74 +msgid "Switch 1G" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:357 +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:187 +msgid "TX Drop" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:263 +msgid "Total" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:452 msgid "Type" msgstr "" -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:351 -#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:421 -msgid "Unbound:" +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:105 +msgid "Warning" +msgstr "" + +#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:168 +msgid "devmem not available on this build" msgstr "" diff --git a/luci-app-airoha-npu/po/zh-cn b/luci-app-airoha-npu/po/zh-cn new file mode 120000 index 000000000..8d69574dd --- /dev/null +++ b/luci-app-airoha-npu/po/zh-cn @@ -0,0 +1 @@ +zh_Hans \ No newline at end of file diff --git a/luci-app-airoha-npu/po/zh_Hans/luci-app-airoha-npu.po b/luci-app-airoha-npu/po/zh_Hans/luci-app-airoha-npu.po new file mode 100644 index 000000000..7d6c2a45b --- /dev/null +++ b/luci-app-airoha-npu/po/zh_Hans/luci-app-airoha-npu.po @@ -0,0 +1,197 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: zh_Hans\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid "%d clients" +msgstr "%d 个客户端" + +msgid "%d cores" +msgstr "%d 个核心" + +msgid "%d used / %d free (%s%%)" +msgstr "已用 %d / 空闲 %d(%s%%)" + +msgid "%s (%d regions)" +msgstr "%s(%d 个区域)" + +msgid "%s MHz (OC)" +msgstr "%s MHz(超频)" + +msgid "8x RISC-V via PCIe RAM" +msgstr "8 个 RISC-V 核心,通过 PCIe RAM 通信" + +msgid "ACTIVE" +msgstr "运行中" + +msgid "Active" +msgstr "运行中" + +msgid "Airoha SoC Status" +msgstr "Airoha SoC 状态" + +msgid "Apply" +msgstr "应用" + +msgid "Applying..." +msgstr "正在应用…" + +msgid "Band %d" +msgstr "频段 %d" + +msgid "Bound" +msgstr "已绑定" + +msgid "CPU Cores" +msgstr "CPU 核心数" + +msgid "CPU Frequency" +msgstr "CPU 频率" + +msgid "CPU set to" +msgstr "CPU 频率已设置为" + +msgid "Critical" +msgstr "严重" + +msgid "Current Frequency" +msgstr "当前频率" + +msgid "Direct PLL. Stock max 1200 MHz. Stable up to 1500 MHz." +msgstr "直接设置 PLL。默认最高 1200 MHz,已测试稳定至 1500 MHz。" + +msgid "Drop" +msgstr "丢包" + +msgid "Error:" +msgstr "错误:" + +msgid "Ethernet" +msgstr "以太网" + +msgid "Failed:" +msgstr "失败:" + +msgid "Fair" +msgstr "一般" + +msgid "Firmware / Clock / Cores" +msgstr "固件 / 时钟 / 核心" + +msgid "Frame Engine" +msgstr "帧引擎" + +msgid "Good" +msgstr "良好" + +msgid "Governor" +msgstr "调频策略" + +msgid "Grant access to Airoha SoC status (NPU + CPU + Overclock)" +msgstr "允许访问 Airoha SoC 状态(NPU、CPU 和超频)" + +msgid "HW Offload:" +msgstr "硬件卸载:" + +msgid "Healthy" +msgstr "正常" + +msgid "Idle" +msgstr "空闲" + +msgid "Index" +msgstr "索引" + +msgid "Internal Switch (1G LAN3/4)" +msgstr "内置交换机(千兆 LAN3/4)" + +msgid "Manages:" +msgstr "管理:" + +msgid "Max Frequency" +msgstr "最高频率" + +msgid "Must be 500-1600 MHz" +msgstr "频率必须在 500–1600 MHz 之间" + +msgid "NPU & Offload Engine" +msgstr "NPU 与卸载引擎" + +msgid "NPU Status" +msgstr "NPU 状态" + +msgid "New Flow" +msgstr "转换后数据流" + +msgid "No clients" +msgstr "无客户端" + +msgid "Not Active" +msgstr "未运行" + +msgid "OFF" +msgstr "关闭" + +msgid "Original Flow" +msgstr "原始数据流" + +msgid "Overclock" +msgstr "超频" + +msgid "PPE Eng 1" +msgstr "PPE 引擎 1" + +msgid "PPE Eng 2" +msgstr "PPE 引擎 2" + +msgid "PPE Engines" +msgstr "PPE 引擎" + +msgid "PPE Flow Offload Entries" +msgstr "PPE 流量卸载条目" + +msgid "PPE init, WDMA rings, flow stats" +msgstr "PPE 初始化、WDMA 环形队列、流量统计" + +msgid "PSE Port Queue Status" +msgstr "PSE 端口队列状态" + +msgid "PSE Shared Buffer" +msgstr "PSE 共享缓冲区" + +msgid "Poor" +msgstr "差" + +msgid "RX Drop" +msgstr "接收丢包" + +msgid "Reserved Memory" +msgstr "保留内存" + +msgid "SoC Status" +msgstr "SoC 状态" + +msgid "State" +msgstr "状态" + +msgid "Switch 1G" +msgstr "千兆交换机" + +msgid "TX Drop" +msgstr "发送丢包" + +msgid "Total" +msgstr "总计" + +msgid "Type" +msgstr "类型" + +msgid "Warning" +msgstr "警告" + +msgid "devmem not available on this build" +msgstr "当前固件不支持 devmem" + +msgid "Frequencies above 1400 MHz may be unstable. Continue?" +msgstr "超过 1400 MHz 的频率可能不稳定,是否继续?" diff --git a/luci-app-airoha-npu/root/usr/libexec/rpcd/luci.airoha_npu b/luci-app-airoha-npu/root/usr/libexec/rpcd/luci.airoha_npu index 329bdad6b..b2bf7ae44 100755 --- a/luci-app-airoha-npu/root/usr/libexec/rpcd/luci.airoha_npu +++ b/luci-app-airoha-npu/root/usr/libexec/rpcd/luci.airoha_npu @@ -291,6 +291,12 @@ set_overclock() { echo '{"error":"missing freq_mhz parameter"}' return fi + case "$target_mhz" in + *[!0-9]*) + echo '{"error":"frequency must be an integer"}' + return + ;; + esac # Validate range (500-1600, crash likely above 1500 at stock voltage) if [ "$target_mhz" -lt 500 ] || [ "$target_mhz" -gt 1600 ]; then @@ -312,9 +318,16 @@ set_overclock() { posdiv=0 pcw_int=$((target_mhz / 50)) fi + if [ $((pcw_int * 50 / (1 << posdiv))) -ne "$target_mhz" ]; then + echo '{"error":"frequency must use 25 MHz steps below 1000 MHz or 50 MHz steps above"}' + return + fi # Lock governor to performance to prevent cpufreq from fighting - echo performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor 2>/dev/null + if ! echo performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor 2>/dev/null; then + echo '{"error":"failed to lock performance governor"}' + return + fi # Read current register values local cur_clkmux=$(($(devmem 0x1fa201e0))) @@ -347,7 +360,7 @@ set_overclock() { fi # Step 6: Wait for PLL lock - sleep 0.02 + sleep 0.02 2>/dev/null || sleep 1 # Step 7: Enable ARMPLL path in CLK_MUX (set bit 0) local cur_clkmux2=$(($(devmem 0x1fa201e0))) @@ -379,6 +392,11 @@ set_overclock() { read_freq=$((read_pcw * 50 / (1 << read_posdiv))) fi + if [ "$read_freq" -ne "$target_mhz" ]; then + printf '{"error":"PLL frequency verification failed","target_mhz":%d,"actual_mhz":%d}' \ + "$target_mhz" "$read_freq" + return + fi printf '{"result":"ok","target_mhz":%d,"actual_mhz":%d,"pcw":%d,"posdiv":%d}' \ "$target_mhz" "$read_freq" "$read_pcw" "$read_posdiv" } diff --git a/luci-app-argon-config/Makefile b/luci-app-argon-config/Makefile index cd23ed8fc..e23f2f6b6 100644 --- a/luci-app-argon-config/Makefile +++ b/luci-app-argon-config/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-argon-config PKG_VERSION:=1.0 -PKG_RELEASE:=4 +PKG_RELEASE:=5 PKG_MAINTAINER:=jerrykuku diff --git a/luci-app-argon-config/root/etc/config/argon b/luci-app-argon-config/root/etc/config/argon index 1519e83d2..a6bdfb87d 100644 --- a/luci-app-argon-config/root/etc/config/argon +++ b/luci-app-argon-config/root/etc/config/argon @@ -3,8 +3,8 @@ config global option dark_primary '#483d8b' option blur '10' option blur_dark '10' - option transparency '0.3' - option transparency_dark '0.3' + option transparency '0.5' + option transparency_dark '0.5' option mode 'dark' option online_wallpaper 'default' diff --git a/luci-app-clashoo/Makefile b/luci-app-clashoo/Makefile index 357d3e149..2e0ed92d1 100644 --- a/luci-app-clashoo/Makefile +++ b/luci-app-clashoo/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-clashoo -PKG_VERSION:=1.30.0 -PKG_RELEASE:=1 +PKG_VERSION:=1.31.0 +PKG_RELEASE:=2 PKG_MAINTAINER:=kenzok8 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) diff --git a/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/config.js b/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/config.js index 8a9bb6b5d..d80e8ef21 100644 --- a/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/config.js +++ b/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/config.js @@ -158,7 +158,8 @@ var callListTemplates = rpc.declare({ object: 'luci.clashoo', method: 'list_temp var callUploadTemplate= rpc.declare({ object: 'luci.clashoo', method: 'upload_template', params: ['name', 'content'], expect: {} }); var callApplyRewrite = rpc.declare({ object: 'luci.clashoo', method: 'apply_rewrite', params: ['base_type','base_name','rewrite_type','rewrite_name','output_name','set_active'], expect: {} }); var callFetchUrl = rpc.declare({ object: 'luci.clashoo', method: 'fetch_rewrite_url', params: ['url','name'], expect: {} }); -var callApplyTplUrl = rpc.declare({ object: 'luci.clashoo', method: 'apply_template_with_url', params: ['template_source','sub_url','output_name','set_active'], expect: {} }); +var callApplyTplUrl = rpc.declare({ object: 'luci.clashoo', method: 'apply_template_with_url', params: ['template_source','sub_pairs','output_name','set_active'], expect: {} }); +var callRegenerate = rpc.declare({ object: 'luci.clashoo', method: 'regenerate_output', params: ['name'], expect: {} }); var callMigrateSbProfile = rpc.declare({ object: 'luci.clashoo', method: 'migrate_singbox_profile', params: ['name'], expect: {} }); var callSmartFlushCache = rpc.declare({ object: 'luci.clashoo', method: 'smart_flush_cache', expect: {} }); var callDetectPrimaryGroup = rpc.declare({ object: 'luci.clashoo', method: 'detect_primary_group', expect: {} }); @@ -596,22 +597,24 @@ function createConfigEditor(mode) { return { el: wrap, textarea: ta, - setValue: function (content) { + setValue: function (content, readOnly) { content = (content == null) ? '' : String(content); + ta.readOnly = !!readOnly; if (content.length > LARGE) { showTextarea(content); return; } - if (cm) { showCM(content); return; } + if (cm) { cm.setOption('readOnly', !!readOnly); showCM(content); return; } loadCodeMirror().then(function () { if (!cm && window.CodeMirror) { cm = window.CodeMirror(host, { value: '', mode: mode, lineNumbers: true, matchBrackets: true, lineWrapping: true, tabSize: 2, indentUnit: 2, - viewportMargin: 60 + viewportMargin: 60, + readOnly: !!readOnly }); if (getThemeClass().indexOf('dark') >= 0) host.classList.add('cl-cm-dark'); } - if (cm) showCM(content); else showTextarea(content); + if (cm) { cm.setOption('readOnly', !!readOnly); showCM(content); } else showTextarea(content); }).catch(function () { showTextarea(content); }); }, getValue: function () { @@ -945,7 +948,66 @@ return view.extend({ }); var tplUrlIn = E('input', { type: 'text', 'class': 'cl-sub-url', placeholder: _("Enter remote template URL, e.g. https://raw.githubusercontent.com/…/Clash.yaml") }); - var subUrlIn = E('input', { type: 'text', 'class': 'cl-sub-url', placeholder: _("Enter subscription URL (injected into template proxy-providers)") }); + /* ── 多 provider pairs 输入 ── */ + var pairs = []; + var pairsBox = E('div', { 'class': 'cl-pairs-box' }); + + function renderPairs() { + pairsBox.innerHTML = ''; + pairs.forEach(function (p, idx) { + var nameIn = E('input', { + type: 'text', 'class': 'cl-sub-url', + style: 'width:120px;flex-shrink:0', + placeholder: _('Provider name'), value: p.name || '' + }); + nameIn.addEventListener('input', function () { pairs[idx].name = nameIn.value; }); + + var urlIn = E('input', { + type: 'text', 'class': 'cl-sub-url', + style: 'flex:1;min-width:0', + placeholder: _('Subscription URL'), value: p.url || '' + }); + urlIn.addEventListener('input', function () { pairs[idx].url = urlIn.value; }); + + var children = [nameIn, urlIn]; + + if (idx === 0) { + var addBtn = E('button', { + 'class': 'btn cbi-button cl-btn-sm', + style: 'flex-shrink:0' + }, '+'); + if (pairs.length >= 5) { + addBtn.setAttribute('disabled', 'disabled'); + addBtn.style.opacity = '0.5'; + addBtn.style.cursor = 'not-allowed'; + } + addBtn.addEventListener('click', function () { + if (pairs.length >= 5) return; + pairs.push({ name: '', url: '' }); + renderPairs(); + }); + children.push(addBtn); + } else { + var delBtn = E('button', { + 'class': 'btn cbi-button cl-btn-sm', + style: 'flex-shrink:0' + }, '−'); + delBtn.addEventListener('click', function () { + pairs.splice(idx, 1); + renderPairs(); + }); + children.push(delBtn); + } + + pairsBox.appendChild(E('div', { + 'class': 'cl-pair-row', + style: 'display:flex;gap:6px;margin-bottom:6px;align-items:center' + }, children)); + }); + } + + pairs.push({ name: '', url: '' }); + renderPairs(); var outNameIn = E('input', { type: 'text', 'class': 'cl-sub-url', placeholder: _("Output file name (without extension, blank to auto-fill)") }); var rwMode = 'local'; @@ -990,12 +1052,13 @@ return view.extend({ var rwApply = function (setActive) { var tplSrc = rwMode === 'local' ? tplSel.value : tplUrlIn.value.trim(); - var subUrl = subUrlIn.value.trim(); var out = outNameIn.value.trim(); if (!tplSrc) { ui.addNotification(null, E('p', rwMode === 'local' ? _("Please select a local template file") : _("Please enter a remote template URL"))); return; } - if (!subUrl) { ui.addNotification(null, E('p', _("Please enter a subscription URL"))); return; } + var validPairs = pairs.filter(function (p) { return p.name.trim() && p.url.trim(); }); + if (validPairs.length === 0) { ui.addNotification(null, E('p', _("Please fill at least one name + URL pair"))); return; } if (!out) { ui.addNotification(null, E('p', _("Please enter an output file name"))); return; } - L.resolveDefault(callApplyTplUrl(tplSrc, subUrl, out, setActive ? '1' : '0'), {}).then(function (r) { + var pairsJson = JSON.stringify(validPairs); + L.resolveDefault(callApplyTplUrl(tplSrc, pairsJson, out, setActive ? '1' : '0'), {}).then(function (r) { ui.addNotification(null, E('p', r && r.success ? (r.message || _("Generated: ") + r.output_name) : (_("Generate failed: ") + (r && r.message || _("Unknown error"))))); if (r && r.success) location.reload(); }); @@ -1019,7 +1082,7 @@ return view.extend({ } }, _("Save")); - var otherEditorBox = E('div', { 'class': 'cl-section cl-card cl-sb-editor' }, [ + var otherEditorBox = E('div', { 'class': 'cl-section cl-card cl-sb-editor cl-inline-editor' }, [ otherEditorTitle, otherEd.el, E('div', { 'class': 'cl-actions cl-sb-row-actions cl-sb-editor-actions' }, [ @@ -1027,93 +1090,194 @@ return view.extend({ E('span', { 'class': 'cl-hint' }, _("Click Save after editing; switching configuration automatically restarts the service")) ]) ]); + var otherEditorHint = otherEditorBox.querySelector('.cl-hint'); - function loadOtherEditor(name, type) { - otherEditorTitle.textContent = _("Editing: ") + name; - otherSaveBtn.removeAttribute('disabled'); + + function loadOtherEditor(name, type, readOnly) { + otherEditorTitle.textContent = (readOnly ? _("Viewing: ") : _("Editing: ")) + name; + if (readOnly) { + otherSaveBtn.style.display = "none"; + } else { + otherSaveBtn.style.display = ""; + otherSaveBtn.removeAttribute('disabled'); + } + if (otherEditorHint) { + otherEditorHint.style.display = ""; + otherEditorHint.textContent = readOnly + ? _("Read-only preview; edit the template and click Update to regenerate") + : _("Click Save after editing; switching configuration automatically restarts the service"); + } otherEd.textarea.dataset.name = name; otherEd.textarea.dataset.type = type; - otherEd.setValue(_("Loading…")); + otherEd.setValue(_("Loading…"), readOnly); L.resolveDefault(callReadOtherConfig(name, type), {}).then(function (r) { - otherEd.setValue(r.content || ''); + otherEd.setValue(r.content || '', readOnly); }); } + var currentEditorCard = null; + var toggleOtherEditor = function (f, type, cardEl) { + if (currentEditorCard === cardEl && otherEditorBox.parentNode) { + otherEditorBox.parentNode.removeChild(otherEditorBox); + currentEditorCard = null; + return; + } + if (otherEditorBox.parentNode) otherEditorBox.parentNode.removeChild(otherEditorBox); + loadOtherEditor(f.name, type, f.generated === true); + cardEl.parentNode.insertBefore(otherEditorBox, cardEl.nextSibling); + currentEditorCard = cardEl; + setTimeout(function(){ + var listWrap = cardEl.closest(".cl-file-list") || cardEl.parentNode; + var section = listWrap.closest(".cl-section") || listWrap.parentNode; + if (listWrap && section) { + var cs = getComputedStyle(section); + var innerW = section.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight); + var listW = listWrap.clientWidth; + var diff = Math.floor((innerW - listW) / 2); + if (diff > 0 && innerW > 0) { + otherEditorBox.style.width = innerW + "px"; + otherEditorBox.style.maxWidth = "none"; + otherEditorBox.style.marginLeft = "-" + diff + "px"; + otherEditorBox.style.marginRight = "-" + diff + "px"; + otherEditorBox.style.boxSizing = "border-box"; + otherEditorBox.style.alignSelf = "stretch"; + } + } + try { otherEditorBox.scrollIntoView({behavior:"smooth", block:"nearest"}); } catch(e) {} + }, 60); + }; + var makeOtherCards = function (files, type) { return files.map(function (f) { + var cardEl; var nameNodes = []; if (f.active) nameNodes.push(E('span', { 'class': 'cl-active-badge' }, _("Active"))); nameNodes.push(E('span', { 'class': 'cl-file-name-text' }, safeText(f.name))); - return E('div', { 'class': 'cl-file-item' + (f.active ? ' is-active' : '') }, [ + var actions = []; + + if (f.generated === true) { + actions.push(E('button', { + 'class': 'btn cbi-button-action cl-btn-sm cl-btn-update-output', + disabled: f.active ? 'disabled' : null, + style: f.active ? 'opacity:.5;cursor:not-allowed' : '', + title: f.active ? _('Active config cannot be updated; switch to another first') : '', + click: function (ev) { + var btn = ev.target; + if (f.active || btn.disabled || clKernelBusy()) return; + clKernelLock(); + btn.disabled = true; + btn.textContent = _("Updating…"); + L.resolveDefault(callRegenerate(f.name), {}).then(function (r) { + if (!r || !r.success) { + ui.addNotification(null, E('p', _("Update failed: ") + ((r && (r.message || r.error)) || ''))); + btn.disabled = false; + btn.textContent = _("Update"); + clKernelUnlock(); + return; + } + if (!r.sub_pairs || r.sub_pairs.length === 0) { + ui.addNotification(null, E('p', _("Update failed: this config has no proxy-providers"))); + btn.disabled = false; + btn.textContent = _("Update"); + clKernelUnlock(); + return; + } + var pairsJson = JSON.stringify(r.sub_pairs); + L.resolveDefault(callApplyTplUrl(r.template_source, pairsJson, f.name, f.active ? '1' : '0'), {}).then(function (r2) { + ui.addNotification(null, E('p', (r2 && r2.success) ? f.name + _(" updated") : _("Update failed"))); + location.reload(); + }).catch(function () { + btn.disabled = false; + btn.textContent = _("Update"); + clKernelUnlock(); + }); + }).catch(function () { + btn.disabled = false; + btn.textContent = _("Update"); + clKernelUnlock(); + }); + } + }, _("Update"))); + } + + actions.push(E('button', { + 'class': 'btn cbi-button cl-btn-sm cl-btn-edit', + click: function (ev) { + var btn = ev.target; + if (btn.disabled) return; + toggleOtherEditor(f, type, cardEl); + } + }, f.generated === true ? _("Show/Hide") : _("Edit"))); + + actions.push(E('button', { + 'class': 'btn cbi-button cl-btn-sm cl-btn-switch', + click: function (ev) { + var btn = ev.target; + if (btn.disabled || clKernelBusy()) return; + clKernelLock(); + btn.disabled = true; + btn.textContent = _("Loading…"); + L.resolveDefault(callSetConfig(f.name), {}).then(function () { + location.reload(); + }).catch(function () { + btn.disabled = false; + btn.textContent = _("Switch Profile"); + clKernelUnlock(); + }); + } + }, _("Switch Profile"))); + + actions.push(E('button', { + 'class': 'btn cbi-button cl-btn-sm cl-btn-delete', + click: function (ev) { + var btn = ev.target; + if (btn.disabled || clKernelBusy()) return; + if (!confirm(_("Delete ") + f.name + '?')) return; + clKernelLock(); + btn.disabled = true; + btn.textContent = _("Loading…"); + L.resolveDefault(callDeleteCfg(f.name, type), {}).then(function () { + location.reload(); + }).catch(function () { + btn.disabled = false; + btn.textContent = _("Delete"); + clKernelUnlock(); + }); + } + }, _("Delete"))); + + cardEl = E('div', { 'class': 'cl-file-item' + (f.active ? ' is-active' : '') }, [ E('div', { 'class': 'cl-file-meta' }, [ E('div', { 'class': 'cl-file-name' }, nameNodes), E('div', { 'class': 'cl-file-size' }, safeText(f.size)) ]), - E('div', { 'class': 'cl-file-actions' }, [ - E('button', { - 'class': 'btn cbi-button cl-btn-sm cl-btn-edit', - click: function (ev) { - var btn = ev.target; - if (btn.disabled) return; - btn.disabled = true; - var orig = btn.textContent; - btn.textContent = _("Loading…"); - Promise.resolve(loadOtherEditor(f.name, type)).then(function () { - btn.disabled = false; - btn.textContent = orig; - }).catch(function () { - btn.disabled = false; - btn.textContent = orig; - }); - } - }, _("Edit")), - E('button', { - 'class': 'btn cbi-button cl-btn-sm cl-btn-switch', - click: function (ev) { - var btn = ev.target; - if (btn.disabled || clKernelBusy()) return; - clKernelLock(); - btn.disabled = true; - btn.textContent = _("Loading…"); - L.resolveDefault(callSetConfig(f.name), {}).then(function () { - location.reload(); - }).catch(function () { - btn.disabled = false; - btn.textContent = _("Switch Profile"); - clKernelUnlock(); - }); - } - }, _("Switch Profile")), - E('button', { - 'class': 'btn cbi-button cl-btn-sm cl-btn-delete', - click: function (ev) { - var btn = ev.target; - if (btn.disabled || clKernelBusy()) return; - if (!confirm(_("Delete ") + f.name + '?')) return; - clKernelLock(); - btn.disabled = true; - btn.textContent = _("Loading…"); - L.resolveDefault(callDeleteCfg(f.name, type), {}).then(function () { - location.reload(); - }).catch(function () { - btn.disabled = false; - btn.textContent = _("Delete"); - clKernelUnlock(); - }); - } - }, _("Delete")) - ]) + E('div', { 'class': 'cl-file-actions' }, actions) ]); + return cardEl; }); }; - var otherFiles = (upFiles || []).map(function(f){ return {f:f, t:'2'}; }) - .concat((customFiles || []).map(function(f){ return {f:f, t:'3'}; })); + var tplOnlyFiles = (customFiles || []).filter(function(f){ return f.generated !== true; }); + var genCustomFiles = (customFiles || []).filter(function(f){ return f.generated === true; }); - var otherCards = otherFiles.map(function(o) { - return makeOtherCards([o.f], o.t)[0]; - }); + var tplCards = tplOnlyFiles.map(function(f){ return makeOtherCards([f], '3')[0]; }); + var genCards = (upFiles || []).map(function(f){ return makeOtherCards([f], '2')[0]; }) + .concat(genCustomFiles.map(function(f){ return makeOtherCards([f], '3')[0]; })); + + var tplListBox = tplCards.length ? E('div', { 'class': 'cl-section cl-card' }, [ + E("h4", {}, _("Template Selection")), + E('div', { 'class': 'cl-fixed-600' }, [ + E('div', { 'class': 'cl-file-list' }, tplCards) + ]) + ]) : null; + + var genListBox = genCards.length ? E('div', { 'class': 'cl-section cl-card' }, [ + E('h4', {}, _("Other Configuration Files (upload / overwrite output)")), + E('div', { 'class': 'cl-fixed-600' }, [ + E('div', { 'class': 'cl-file-list' }, genCards) + ]) + ]) : null; var sections = [ E('div', { 'class': 'cl-section cl-card' }, [ @@ -1138,15 +1302,8 @@ return view.extend({ ]) ]; - if (otherFiles.length) { - sections.push(E('div', { 'class': 'cl-section cl-card' }, [ - E('h4', {}, _("Other Configuration Files (upload / overwrite output)")), - E('div', { 'class': 'cl-fixed-600' }, [ - E('div', { 'class': 'cl-file-list' }, otherCards) - ]) - ])); - sections.push(otherEditorBox); - } + if (genListBox) sections.push(genListBox); + if (tplListBox) sections.push(tplListBox); sections.push( E('div', { 'class': 'cl-section cl-card' }, [ @@ -1161,8 +1318,8 @@ return view.extend({ E('div', { 'class': 'cl-rw-divider' }), E('div', { 'class': 'cl-rewrite-group cl-rewrite-group-input' }, [ E('div', { 'class': 'cl-rewrite-group-title' }, _("Information Input")), - subUrlIn, outNameIn, + pairsBox, E('div', { 'class': 'cl-actions cl-rewrite-actions' }, [ E('button', { 'class': 'btn cbi-button cl-btn-sm', click: function(){ rwApply(false); } }, _("Generate Configuration")), E('button', { 'class': 'btn cbi-button-action cl-btn-sm cl-btn-generate-switch', click: function(){ rwApply(true); } }, _("Apply Configuration")) @@ -1414,7 +1571,7 @@ return view.extend({ o = s.option(form.DynamicList, 'fake_ip_filter', _("Fake-IP Filter Domains")); o.placeholder = '+.lan / geosite:cn / RULE-SET,cn_domain,real-ip'; - o.description = _("Blocklist mode: enter domains or shorthand like geosite:cn. Rule mode: entries are converted to rule syntax automatically, such as GEOSITE,cn,real-ip / RULE-SET,xxx,real-ip; usually add MATCH,fake-ip at the end as fallback. geosite:cn automatically uses built-in cn.mrs acceleration and avoids loading the 10MB geosite.dat. Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains."); + o.description = _("Blocklist mode: enter domains or shorthand like geosite:cn. Rule mode: entries are converted to rule syntax automatically, such as RULE-SET,xxx,real-ip / DOMAIN-SUFFIX,xxx,real-ip; usually add MATCH,fake-ip at the end as fallback. geosite:cn becomes RULE-SET,cn_domain,real-ip via built-in cn.mrs acceleration (other geosite:xxx becomes GEOSITE,xxx,real-ip). Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains."); o.depends('enhanced_mode', 'fake-ip'); o.remove = function () {}; o = s.option(form.DynamicList, 'default_nameserver', 'Bootstrap DNS'); diff --git a/luci-app-clashoo/po/templates/clashoo.pot b/luci-app-clashoo/po/templates/clashoo.pot index d31f14df6..d9e67d424 100644 --- a/luci-app-clashoo/po/templates/clashoo.pot +++ b/luci-app-clashoo/po/templates/clashoo.pot @@ -203,7 +203,7 @@ msgstr "" msgid "Blocklist (listed items use real IP, default)" msgstr "" -msgid "Blocklist mode: enter domains or shorthand like geosite:cn. Rule mode: entries are converted to rule syntax automatically, such as GEOSITE,cn,real-ip / RULE-SET,xxx,real-ip; usually add MATCH,fake-ip at the end as fallback. geosite:cn automatically uses built-in cn.mrs acceleration and avoids loading the 10MB geosite.dat. Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains." +msgid "Blocklist mode: enter domains or shorthand like geosite:cn. Rule mode: entries are converted to rule syntax automatically, such as RULE-SET,xxx,real-ip / DOMAIN-SUFFIX,xxx,real-ip; usually add MATCH,fake-ip at the end as fallback. geosite:cn becomes RULE-SET,cn_domain,real-ip via built-in cn.mrs acceleration (other geosite:xxx becomes GEOSITE,xxx,real-ip). Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains." msgstr "" msgid "Bypass China IPv4" diff --git a/luci-app-clashoo/po/zh-cn/clashoo.po b/luci-app-clashoo/po/zh-cn/clashoo.po index fe30cb1b9..20a4e4bf6 100644 --- a/luci-app-clashoo/po/zh-cn/clashoo.po +++ b/luci-app-clashoo/po/zh-cn/clashoo.po @@ -213,8 +213,8 @@ msgstr "黑名单" msgid "Blocklist (listed items use real IP, default)" msgstr "黑名单(列入走真 IP,默认)" -msgid "Blocklist mode: enter domains or shorthand like geosite:cn. Rule mode: entries are converted to rule syntax automatically, such as GEOSITE,cn,real-ip / RULE-SET,xxx,real-ip; usually add MATCH,fake-ip at the end as fallback. geosite:cn automatically uses built-in cn.mrs acceleration and avoids loading the 10MB geosite.dat. Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains." -msgstr "黑名单模式:填域名或 geosite:cn 简写。规则模式:条目会自动转成 GEOSITE,cn,real-ip / RULE-SET,xxx,real-ip 这种与 rules 同语法的写法,末尾通常加一条 MATCH,fake-ip 兜底。填 geosite:cn 会自动改用内置 cn.mrs 加速,免加载 10MB geosite.dat。白名单模式下随包自带的默认项含义相反,会被跳过,请自行填写需要 Fake-IP 的域名。" +msgid "Blocklist mode: enter domains or shorthand like geosite:cn. Rule mode: entries are converted to rule syntax automatically, such as RULE-SET,xxx,real-ip / DOMAIN-SUFFIX,xxx,real-ip; usually add MATCH,fake-ip at the end as fallback. geosite:cn becomes RULE-SET,cn_domain,real-ip via built-in cn.mrs acceleration (other geosite:xxx becomes GEOSITE,xxx,real-ip). Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains." +msgstr "黑名单模式:填域名或 geosite:cn 简写。规则模式:条目会自动转成 RULE-SET,xxx,real-ip / DOMAIN-SUFFIX,xxx,real-ip 这种与 rules 同语法的写法,末尾通常加一条 MATCH,fake-ip 兜底。geosite:cn 会转成 RULE-SET,cn_domain,real-ip,走内置 cn.mrs 加速(其它 geosite:xxx 则转成 GEOSITE,xxx,real-ip)。白名单模式下随包自带的默认项含义相反,会被跳过,请自行填写需要 Fake-IP 的域名。" msgid "Bypass China IPv4" msgstr "大陆 IPv4 绕过" @@ -1577,3 +1577,21 @@ msgstr "✗ 启动失败: " msgid "✗ Submission failed" msgstr "✗ 提交失败" + +msgid "Please fill at least one name + URL pair" +msgstr "请至少填写一组「名称 + 订阅链接」" + +msgid "Provider name" +msgstr "订阅源名称" + +msgid "Update failed: this config has no proxy-providers" +msgstr "更新失败:该配置没有 proxy-providers" + +msgid "Viewing: " +msgstr "查看:" + +msgid "Read-only preview; edit the template and click Update to regenerate" +msgstr "只读预览;修改请编辑模板后点「更新」重新生成" + +msgid "Active config cannot be updated; switch to another first" +msgstr "活跃配置无法更新;请先切换到其他配置" diff --git a/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo b/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo index 2a9884d03..97eea06ba 100755 --- a/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo +++ b/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo @@ -3042,30 +3042,71 @@ if (!dir) return { files: [] }; let active = uci_get('clashoo', 'config', 'use_config'); let active_name = replace(active, /^.*\//, ''); let files = []; -for (let f in (glob(dir + '/*.yaml') || [])) { + +let need_check = (req.args?.type == '3'); +let collect = function(ext) { +for (let f in (glob(dir + '/*' + ext) || [])) { let name = replace(f, dir + '/', ''); let fi = stat(f); +let generated = false; +if (need_check && (!fi || fi.size <= 524288)) { +let content = readfile(f) || ''; +for (let ln in split(content, '\n')) { +if (match(ln, /^x-clashoo:/)) { generated = true; break; } +} +} push(files, { name, mtime: fi ? ('' + fi.mtime) : null, size: fi ? (int(fi.size / 1024) + ' KB') : null, -active: name == active_name -}); -} -for (let f in (glob(dir + '/*.yml') || [])) { -let name = replace(f, dir + '/', ''); -let fi = stat(f); -push(files, { -name, -mtime: fi ? ('' + fi.mtime) : null, -size: fi ? (int(fi.size / 1024) + ' KB') : null, -active: name == active_name +active: name == active_name, +generated: generated }); } +}; +collect('.yaml'); +collect('.yml'); return { files, active: active_name }; } }, +regenerate_output: { +args: { name: 'name' }, +call: function(req) { +let name = ensure_yaml_name(req.args?.name || ''); +if (!name) +return { success: false, error: 'invalid_name', message: '文件名无效' }; +let path = TEMPLATE_DIR + '/' + name; +if (!access(path, 'r')) +return { success: false, error: 'not_found', message: '文件不存在' }; + +let p = popen("yq -M -o=json '{\"template\": .\"x-clashoo\".template // \"\", \"pairs\": [.[\"proxy-providers\"] | to_entries[] | {\"name\": .key, \"url\": .value.url}]}' " + shell_quote(path) + " 2>/dev/null"); +let out = ''; +if (p) { out = trim(p.read('all')); p.close(); } + +let template = ''; +let pairs = []; +try { +let data = json(out || '{}'); +template = data.template || ''; +if (type(data.pairs) == 'array') { +for (let item in data.pairs) { +if (item && item.name && item.url) push(pairs, item); +} +} +} catch (e) {} + +if (!template) +return { success: false, error: 'no_meta', message: '该文件不是模板生成的配置' }; + +return { +success: true, +template_source: template, +sub_pairs: pairs +}; +} +}, + delete_config: { args: { name: 'name', type: 'type' }, call: function(req) { @@ -3370,25 +3411,38 @@ return { success: true, enabled: enabled, interval: interval }; apply_template_with_url: { args: { template_source: 'template_source', -sub_url: 'sub_url', +sub_pairs: 'sub_pairs', output_name: 'output_name', set_active: 'set_active' }, call: function(req) { let tpl_src = trim(req.args?.template_source || ''); -let sub_url = trim(req.args?.sub_url || ''); +let pairs_raw = trim(req.args?.sub_pairs || ''); let output_name = ensure_yaml_name(req.args?.output_name || ''); if (!output_name) output_name = 'tpl-rewrite.yaml'; -let uniq_name = replace(output_name, /\.(yaml|yml)$/, ''); let set_active = (req.args?.set_active || '') == '1'; if (!tpl_src) return { success: false, error: 'invalid_template', message: '模板来源不能为空' }; -if (!sub_url || !match(sub_url, /^https?:\/\//)) -return { success: false, error: 'invalid_sub_url', message: '订阅 URL 无效' }; + +let pairs = []; +try { +let parsed = json(pairs_raw || '[]'); +if (type(parsed) == 'array') { +for (let p in parsed) { +if (p && p.name && p.url && match(p.url, /^https?:\/\//)) +push(pairs, { name: trim(p.name), url: trim(p.url) }); +} +} +} catch (err) { +return { success: false, error: 'invalid_pairs', message: '订阅参数格式错误' }; +} +if (length(pairs) == 0) +return { success: false, error: 'empty_pairs', message: '请至少填一行有效的 名字+URL' }; let tmp_tpl = '/tmp/clashoo_tpl_in.yaml'; let tmp_out = '/tmp/clashoo_tpl_out.yaml'; +let tmp_pairs = '/tmp/clashoo_tpl_pairs.json'; let out_dir = '/usr/share/clashoo/config/custom'; system('mkdir -p ' + shell_quote(out_dir)); @@ -3413,7 +3467,6 @@ if (system('cp -f ' + shell_quote(local_path) + ' ' + shell_quote(tmp_tpl)) != 0 return { success: false, error: 'copy_failed', message: '读取本地模板失败' }; } -/* 用 yq 把所有 proxy-providers 条目的 url 替换为订阅链接 */ if (system('command -v yq >/dev/null 2>&1') != 0) return { success: false, error: 'missing_yq', message: '缺少 yq,无法注入订阅 URL' }; @@ -3423,15 +3476,29 @@ return { success: false, error: 'missing_yq', message: '缺少 yq,无法注入 if (!access(exploded, "r")) { system("cp -f " + shell_quote(tmp_tpl) + " " + shell_quote(exploded)); } - /* 替换 proxy-providers.url 并把 provider 名改成唯一名(同步 proxy-groups 的 use)*/ - let inject_cmd = "URL=" + shell_quote(sub_url) + " NAME=" + shell_quote(uniq_name) - + " yq e '.[\"proxy-providers\"][].url = env(URL) | (.[\"proxy-providers\"] | keys | map({\"key\": ., \"value\": strenv(NAME) + \"-\" + .}) | from_entries) as $rename | .[\"proxy-providers\"] |= with_entries(.key = $rename[.key]) | (.[\"proxy-groups\"][] | select(has(\"use\")) | .use) |= map($rename[.] // .)' " + + /* pairs 写到临时 JSON 文件 */ + writefile(tmp_pairs, sprintf('%.J', pairs)); + + let tpl_label = match(tpl_src, /^https?:\/\//) ? tpl_src : replace(tpl_src, /^.*\//, ""); + + let yq_expr = '(load("' + tmp_pairs + '")) as $pairs | ' + + '(.["proxy-providers"] | keys) as $oldkeys | ' + + '($oldkeys | to_entries | map(.key as $i | .value as $k | {"key": $k, "value": ($pairs[$i].name // $k)}) | from_entries) as $ren | ' + + '($oldkeys | to_entries | map(select(.key < ($pairs | length)) | .key as $i | .value as $k | {"key": $k, "value": $pairs[$i].url}) | from_entries) as $reurl | ' + + '.["proxy-providers"] |= with_entries(.key as $old | .key = ($ren[$old] // $old) | .value.url = ($reurl[$old] // .value.url)) | ' + + '(.["proxy-groups"][] | select(has("use")) | .use) |= map($ren[.] // .) | ' + + '."x-clashoo" = {"template": strenv(TPL_LABEL)}'; + + let inject_cmd = "TPL_LABEL=" + shell_quote(tpl_label) + + " yq -M e '" + yq_expr + "' " + shell_quote(exploded) + " > " + shell_quote(tmp_out); + if (system(inject_cmd) != 0 || !access(tmp_out, "r")) { - system("rm -f " + shell_quote(exploded)); + system("rm -f " + shell_quote(exploded) + " " + shell_quote(tmp_pairs)); return { success: false, error: "inject_failed", message: "订阅 URL 注入失败" }; } - system("rm -f " + shell_quote(exploded)); + system("rm -f " + shell_quote(exploded) + " " + shell_quote(tmp_pairs)); let out_path = out_dir + '/' + output_name; if (system('mv -f ' + shell_quote(tmp_out) + ' ' + shell_quote(out_path)) != 0) @@ -3443,20 +3510,20 @@ system('rm -f ' + shell_quote(tmp_tpl)); /* 只把独立引用项"直连"转成 DIRECT,组名里的"直连"不动 */ system("sed -i 's/^\\( *- *\\)直连$/\\1DIRECT/' " + shell_quote(out_path) + " 2>/dev/null"); system("yq e '(.proxy-groups[] | select(has(\"proxies\")) | .proxies) |= map(sub(\"^直连$\", \"DIRECT\"))' " + shell_quote(out_path) + " -i 2>/dev/null"); + /* 用 mixin.uc + yq merge 叠加 UCI 覆盖(端口、routing-mark、dns 等) */ let mixin_script = "/usr/share/clashoo/runtime/mixin.uc"; if (access(mixin_script, "r") && system("command -v yq >/dev/null 2>&1") == 0) { let mixin_yaml = "/tmp/_clashoo_mixin.yaml"; let merged_out = out_path + ".merged"; system("ucode " + shell_quote(mixin_script) + " 2>/dev/null | yq -M -p json -o yaml > " + shell_quote(mixin_yaml) + " 2>/dev/null"); - system("yq -M eval-all '(. as $item ireduce ({}; . * $item)) as $m | [\"port\",\"socks-port\",\"redir-port\",\"tproxy-port\",\"mixed-port\",\"allow-lan\",\"bind-address\",\"mode\",\"log-level\",\"ipv6\",\"interface-name\",\"routing-mark\",\"external-controller\",\"external-ui\",\"secret\",\"tun\",\"listeners\",\"tunnels\",\"dns\",\"hosts\",\"profile\",\"geodata-mode\",\"geodata-loader\",\"geox-url\",\"proxy-providers\",\"proxies\",\"proxy-groups\",\"rule-providers\",\"rules\",\"sub-rules\",\"ntp\",\"authentication\",\"sniffer\",\"experimental\"] as $order | ($order | to_entries | map({\"key\": .value, \"value\": .key}) | from_entries) as $rank | $m | to_entries | sort_by(.key as $k | $rank[$k] // 999) | from_entries' " + shell_quote(out_path) + " " + shell_quote(mixin_yaml) + " > " + shell_quote(merged_out) + " 2>/dev/null"); + system("yq -M eval-all '(. as $item ireduce ({}; . * $item)) as $m | [\"x-clashoo\",\"port\",\"socks-port\",\"redir-port\",\"tproxy-port\",\"mixed-port\",\"allow-lan\",\"bind-address\",\"mode\",\"log-level\",\"ipv6\",\"interface-name\",\"routing-mark\",\"external-controller\",\"external-ui\",\"secret\",\"tun\",\"listeners\",\"tunnels\",\"dns\",\"hosts\",\"profile\",\"geodata-mode\",\"geodata-loader\",\"geox-url\",\"proxy-providers\",\"proxies\",\"proxy-groups\",\"rule-providers\",\"rules\",\"sub-rules\",\"ntp\",\"authentication\",\"sniffer\",\"experimental\"] as $order | ($order | to_entries | map({\"key\": .value, \"value\": .key}) | from_entries) as $rank | $m | to_entries | sort_by(.key as $k | $rank[$k] // 999) | from_entries' " + shell_quote(out_path) + " " + shell_quote(mixin_yaml) + " > " + shell_quote(merged_out) + " 2>/dev/null"); if (access(merged_out, "r")) { system("mv -f " + shell_quote(merged_out) + " " + shell_quote(out_path)); } system("rm -f " + shell_quote(mixin_yaml)); } - /* ── mihomo -t 预校验(搜所有 mihomo 变体)── */ let _core_bin = ''; for (let _cm in ['mihomo', 'clash-meta', 'clash']) { @@ -3488,7 +3555,6 @@ system('service ' + get_service_name() + ' restart >/dev/null 2>&1'); return { success: true, output_name, output_path: out_path, message: '模板已应用: ' + output_name }; } }, - apply_rewrite: { args: { base_type: 'base_type', diff --git a/luci-app-daede/Makefile b/luci-app-daede/Makefile index f6c18c31b..2df2c50c9 100644 --- a/luci-app-daede/Makefile +++ b/luci-app-daede/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-daede PKG_VERSION:=1.15 -PKG_RELEASE:=46 +PKG_RELEASE:=47 PKG_MAINTAINER:=kenzok8 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) diff --git a/luci-app-daede/htdocs/luci-static/resources/view/daede/styles.js b/luci-app-daede/htdocs/luci-static/resources/view/daede/styles.js index 2525f8998..0aaf184ca 100644 --- a/luci-app-daede/htdocs/luci-static/resources/view/daede/styles.js +++ b/luci-app-daede/htdocs/luci-static/resources/view/daede/styles.js @@ -102,18 +102,21 @@ const CSS = [ '.dd-adv:not(.dd-closed) .dd-adv-chevron{transform:rotate(90deg)}', '.dd-adv-body{margin-top:8px;padding:2px 4px 4px}', '.dd-adv.dd-closed .dd-adv-body{display:none}', - '.dd-editor{width:100%;min-height:460px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:12px;line-height:1.5;box-sizing:border-box;resize:vertical;border-radius:6px 6px 0 0}', + /* display:block kills the ~5px inline-block baseline gap that made the + overlay pre (inset:0) ~5px taller than the textarea — the two layers' + scrollbars and glyphs would otherwise drift apart on mobile */ + '.dd-editor{display:block;width:100%;min-height:460px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:12px;line-height:1.5;box-sizing:border-box;resize:vertical;border-radius:6px 6px 0 0}', /* syntax-highlight overlay: a
 sits behind a transparent ",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.readonly!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ha=/^\s+/,ia=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja=/<([\w:]+)/,ka=/\s*$/g,ra={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sa=da(y),ta=sa.appendChild(y.createElement("div"));ra.optgroup=ra.option,ra.tbody=ra.tfoot=ra.colgroup=ra.caption=ra.thead,ra.th=ra.td;function ua(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ua(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function va(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wa(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function ya(a){var b=pa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function za(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Aa(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Ba(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xa(b).text=a.text,ya(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!ga.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ta.innerHTML=a.outerHTML,ta.removeChild(f=ta.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ua(f),h=ua(a),g=0;null!=(e=h[g]);++g)d[g]&&Ba(e,d[g]);if(b)if(c)for(h=h||ua(a),d=d||ua(f),g=0;null!=(e=h[g]);g++)Aa(e,d[g]);else Aa(a,f);return d=ua(f,"script"),d.length>0&&za(d,!i&&ua(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=da(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(la.test(f)){h=h||o.appendChild(b.createElement("div")),i=(ja.exec(f)||["",""])[1].toLowerCase(),l=ra[i]||ra._default,h.innerHTML=l[1]+f.replace(ia,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&ha.test(f)&&p.push(b.createTextNode(ha.exec(f)[0])),!k.tbody){f="table"!==i||ka.test(f)?""!==l[1]||ka.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ua(p,"input"),va),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ua(o.appendChild(f),"script"),g&&za(h),c)){e=0;while(f=h[e++])oa.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ua(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&za(ua(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ua(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fa,""):void 0;if(!("string"!=typeof a||ma.test(a)||!k.htmlSerialize&&ga.test(a)||!k.leadingWhitespace&&ha.test(a)||ra[(ja.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ia,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ua(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ua(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&na.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ua(i,"script"),xa),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ua(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,ya),j=0;f>j;j++)d=g[j],oa.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qa,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Ca,Da={};function Ea(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fa(a){var b=y,c=Da[a];return c||(c=Ea(a,b),"none"!==c&&c||(Ca=(Ca||m("