🗽 Sync 2026-10-26 14:45:36

This commit is contained in:
github-actions[bot] committed 2026-10-26 14:45:36 +08:00
1 parent d9474ffda8
commit ac10e08480
60 files changed
+4186 -1605

No files matched your search

+49 -11
View File
@@ -1,19 +1,20 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=clashoo PKG_NAME:=clashoo
PKG_SHORT_SHA:=6780eee PKG_UPSTREAM_VERSION:=1.19.33
PKG_COMMIT_DATE:=2026.10.25 PKG_UPSTREAM_TAG:=v$(PKG_UPSTREAM_VERSION)
PKG_SHORT_SHA:=3f27ef3
PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA) PKG_VERSION:=2026.10.25.$(PKG_UPSTREAM_VERSION)
PKG_RELEASE:=18 PKG_RELEASE:=19
PKG_SOURCE:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA).tar.gz PKG_SOURCE:=$(PKG_NAME)-stable-$(PKG_UPSTREAM_TAG)-$(PKG_SHORT_SHA).tar.gz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-stable-$(PKG_UPSTREAM_TAG)-$(PKG_SHORT_SHA)
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src
PKG_SOURCE_VERSION:=d831028ec8a3d336ea83295bea36c32fdc973ede PKG_SOURCE_VERSION:=57468807be288af4250181a024e29c2971eb228c
PKG_HASH:=skip PKG_HASH:=skip
PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA) PKG_BUILD_VERSION:=$(PKG_UPSTREAM_TAG)
PKG_LICENSE:=GPL3.0+ PKG_LICENSE:=GPL3.0+
PKG_MAINTAINER:=kenzok8 PKG_MAINTAINER:=kenzok8
@@ -50,8 +51,11 @@ endef
define Package/clashoo/install define Package/clashoo/install
$(call GoPackage/Package/Install/Bin,$(1)) $(call GoPackage/Package/Install/Bin,$(1))
$(INSTALL_DIR) $(1)/usr/libexec/clashoo
$(INSTALL_DIR) $(1)/usr/bin $(INSTALL_DIR) $(1)/usr/bin
ln -sf /usr/bin/mihomo $(1)/usr/bin/clash-meta mv -f $(1)/usr/bin/mihomo $(1)/usr/libexec/clashoo/mihomo-stable
ln -sf /usr/libexec/clashoo/mihomo-stable $(1)/usr/bin/mihomo
ln -sf /usr/libexec/clashoo/mihomo-stable $(1)/usr/bin/clash-meta
$(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_DIR) $(1)/etc/config $(INSTALL_DIR) $(1)/etc/config
@@ -82,10 +86,44 @@ define Package/clashoo/install
$(RM) $(1)/usr/share/clashoo/core_down_complete $(RM) $(1)/usr/share/clashoo/core_down_complete
endef endef
define Package/clashoo/preinst
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
save_core() {
src="$$1"
dst="$$2"
[ -f "$$src" ] && [ ! -L "$$src" ] || return 0
if [ -e "$$dst" ] || [ -L "$$dst" ]; then return 0; fi
mkdir -p "$$(dirname "$$dst")" || return 1
tmp="$$dst.tmp.$$$$"
cp "$$src" "$$tmp" && chmod 0755 "$$tmp" && cmp -s "$$src" "$$tmp" || {
rm -f "$$tmp"
return 1
}
if command -v sha256sum >/dev/null 2>&1; then
old_hash="$$(sha256sum "$$src" | awk '{print $$1}')"
new_hash="$$(sha256sum "$$tmp" | awk '{print $$1}')"
[ "$$old_hash" = "$$new_hash" ] || {
rm -f "$$tmp"
return 1
}
fi
mv -f "$$tmp" "$$dst"
}
save_core /usr/bin/mihomo /usr/share/clashoo/bin/mihomo-alpha || exit 1
save_core /usr/bin/mihomo-stable /usr/share/clashoo/bin/mihomo-stable || exit 1
if [ -x /usr/bin/clash-meta ] && [ ! -L /usr/bin/clash-meta ] && [ ! -e /usr/share/clashoo/bin/mihomo-stable ]; then
if ! /usr/bin/clash-meta -v 2>/dev/null | grep -Eiq '(alpha|beta|rc|pre|smart)'; then
save_core /usr/bin/clash-meta /usr/share/clashoo/bin/mihomo-stable || exit 1
fi
fi
fi
exit 0
endef
define Package/clashoo/postinst define Package/clashoo/postinst
#!/bin/sh #!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then if [ -z "$${IPKG_INSTROOT}" ]; then
[ -x /usr/bin/mihomo ] && [ ! -x /usr/bin/clash-meta ] && ln -sf /usr/bin/mihomo /usr/bin/clash-meta
rm -f /usr/share/clashoo/check_luci_version.sh /usr/share/clashoo/check_clash_meta_version.sh /usr/share/clashoo/check_mihomo_core_version.sh rm -f /usr/share/clashoo/check_luci_version.sh /usr/share/clashoo/check_clash_meta_version.sh /usr/share/clashoo/check_mihomo_core_version.sh
rm -f /usr/share/clashoo/cuslist.sh /usr/share/clashoo/groups.sh /usr/share/clashoo/load_groups.sh /usr/share/clashoo/panel_diag.sh rm -f /usr/share/clashoo/cuslist.sh /usr/share/clashoo/groups.sh /usr/share/clashoo/load_groups.sh /usr/share/clashoo/panel_diag.sh
rm -f /usr/share/clashoo/rmlist.sh /usr/share/clashoo/uplist.sh /usr/share/clashoo/rpc_restart_async.sh /usr/share/clashoo/rpc_update_china_ip_async.sh rm -f /usr/share/clashoo/rmlist.sh /usr/share/clashoo/uplist.sh /usr/share/clashoo/rpc_restart_async.sh /usr/share/clashoo/rpc_update_china_ip_async.sh
+2 -2
View File
@@ -9,7 +9,7 @@ config clashoo 'config'
option dash_port '9090' option dash_port '9090'
option dashboard_panel 'zashboard' option dashboard_panel 'zashboard'
option level 'info' option level 'info'
option core '3' option core '2'
option allow_lan '1' option allow_lan '1'
option dnsforwader '1' option dnsforwader '1'
option subcri 'meta' option subcri 'meta'
@@ -28,7 +28,7 @@ config clashoo 'config'
option listen_port '1053' option listen_port '1053'
option interf '0' option interf '0'
option download_core 'amd64-compatible' option download_core 'amd64-compatible'
option dcore '3' option dcore '2'
option same_tag '1' option same_tag '1'
option geoip_source '3' option geoip_source '3'
option geoip_format 'mmdb' option geoip_format 'mmdb'
+47 -37
View File
@@ -169,43 +169,54 @@ cleanup_stale_core_nft() {
} }
_core_candidates() { _core_candidates() {
echo "/usr/bin/mihomo-stable /usr/bin/mihomo /usr/bin/clash-meta /etc/clashoo/clash" echo "/usr/share/clashoo/bin/mihomo-stable /usr/libexec/clashoo/mihomo-stable /usr/share/clashoo/bin/mihomo-alpha /usr/bin/mihomo /usr/bin/clash-meta /etc/clashoo/clash"
} }
_migrate_legacy_mihomo_stable() { _migrate_legacy_mihomo_stable() {
local legacy=/usr/bin/clash-meta version local legacy=/usr/bin/clash-meta target=/usr/share/clashoo/bin/mihomo-stable version staged
[ -x /usr/bin/mihomo-stable ] && return 0 [ -x "$target" ] && return 0
[ -x "$legacy" ] && [ ! -L "$legacy" ] || return 0 [ -x "$legacy" ] && [ ! -L "$legacy" ] || return 0
version=$("$legacy" -v 2>/dev/null | head -n 1) version=$("$legacy" -v 2>/dev/null | head -n 1)
[ -n "$version" ] || return 0 [ -n "$version" ] || return 0
echo "$version" | grep -Eiq '(alpha|beta|rc|pre|smart)' && return 0 echo "$version" | grep -Eiq '(alpha|beta|rc|pre|smart)' && return 0
cp -f "$legacy" /usr/bin/mihomo-stable 2>/dev/null && chmod 0755 /usr/bin/mihomo-stable mkdir -p "$(dirname "$target")" || return 1
staged="${target}.$$"
cp -f "$legacy" "$staged" 2>/dev/null && chmod 0755 "$staged" && mv -f "$staged" "$target"
} }
_fallback_missing_mihomo_core() { _mihomo_stable_binary() {
local selected fallback local bundled=/usr/libexec/clashoo/mihomo-stable manual=/usr/share/clashoo/bin/mihomo-stable bundled_ver manual_ver
is_singbox_selected && return 0 # A successful custom download is an explicit override, even if its version is older.
selected="$(uci -q get clashoo.config.core 2>/dev/null)" if [ -x "$manual" ] &&
case "$selected" in [ "$(sed -n '1{s/^[[:space:]]*//;s/[[:space:]]*$//;p;}' /usr/share/clashoo/clash_meta_version 2>/dev/null)" = "custom-url" ]; then
2) echo "$manual"
[ -x /usr/bin/mihomo-stable ] && return 0 return 0
[ -x /usr/bin/mihomo ] || return 0 fi
fallback=3 [ -x "$bundled" ] || { [ -x "$manual" ] && { echo "$manual"; return 0; }; return 1; }
;; [ -x "$manual" ] || { echo "$bundled"; return; }
3) bundled_ver="$("$bundled" -v 2>/dev/null | awk '{for (i = 1; i < NF; i++) if ($i == "Meta") {print $(i + 1); exit}}')"
[ -x /usr/bin/mihomo ] && return 0 manual_ver="$("$manual" -v 2>/dev/null | awk '{for (i = 1; i < NF; i++) if ($i == "Meta") {print $(i + 1); exit}}')"
[ -x /usr/bin/mihomo-stable ] || return 0 bundled_ver="${bundled_ver#v}"
fallback=2 manual_ver="${manual_ver#v}"
;; printf '%s\n' "$bundled_ver" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || bundled_ver=""
*) return 0 ;; printf '%s\n' "$manual_ver" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || manual_ver=""
esac if [ -n "$bundled_ver" ] && [ -n "$manual_ver" ]; then
uci -q set clashoo.config.core="$fallback" if awk -v manual="$manual_ver" -v bundled="$bundled_ver" 'BEGIN {
uci -q set clashoo.config.dcore="$fallback" split(manual, m, "."); split(bundled, b, ".");
uci -q commit clashoo >/dev/null 2>&1 for (i = 1; i <= 3; i++) {
if [ "$fallback" = "3" ]; then if (m[i] + 0 > b[i] + 0) exit 0;
log_msg "Stable core missing, falling back to Alpha" "稳定版内核缺失,自动回退到 Alpha" if (m[i] + 0 < b[i] + 0) exit 1;
}
exit 0;
}'; then
echo "$manual"
else
echo "$bundled"
fi
elif [ -n "$manual_ver" ]; then
echo "$manual"
else else
log_msg "Alpha core missing, falling back to stable" "Alpha 内核缺失,自动回退到稳定版" echo "$bundled"
fi fi
} }
@@ -220,11 +231,11 @@ _detect_selected_core() {
return 1 return 1
;; ;;
2|mihomo) 2|mihomo)
[ -x /usr/bin/mihomo-stable ] && echo /usr/bin/mihomo-stable && return _mihomo_stable_binary
return 1 return $?
;; ;;
3|mihomo-alpha) 3|mihomo-alpha)
[ -x /usr/bin/mihomo ] && echo /usr/bin/mihomo && return [ -x /usr/share/clashoo/bin/mihomo-alpha ] && echo /usr/share/clashoo/bin/mihomo-alpha && return
return 1 return 1
;; ;;
esac esac
@@ -233,7 +244,7 @@ _detect_selected_core() {
core_supports_advanced_features() { core_supports_advanced_features() {
case "$CLASH" in case "$CLASH" in
*/mihomo|*/mihomo-stable|*/clash-meta|*/smart) */mihomo|*/mihomo-stable|*/mihomo-alpha|*/clash-meta|*/smart)
return 0 return 0
;; ;;
*) *)
@@ -2126,12 +2137,12 @@ _start_validate_core() {
echo "Clashoo" > "$REAL_LOG" echo "Clashoo" > "$REAL_LOG"
return 1 return 1
elif [ "${core:-0}" -eq 2 ] && [ ! -x "$CLASH" ]; then elif [ "${core:-0}" -eq 2 ] && [ ! -x "$CLASH" ]; then
log_msg "mihomo core not found, detected binary is unavailable: $CLASH" "找不到可用的 mihomo 内核: $CLASH" log_msg "mihomo stable core not found, detected binary is unavailable: $CLASH" "找不到可用的 mihomo 稳定版内核: $CLASH"
sleep 1 sleep 1
echo "Clashoo" > "$REAL_LOG" echo "Clashoo" > "$REAL_LOG"
return 1 return 1
elif [ "${core:-0}" -eq 3 ] && [ ! -x "$CLASH" ]; then elif [ "${core:-0}" -eq 3 ] && [ ! -x "$CLASH" ]; then
log_msg "mihomo core not found, detected binary is unavailable: $CLASH" "找不到可用的 mihomo 内核: $CLASH" log_msg "mihomo Alpha core not found, detected binary is unavailable: $CLASH" "找不到可用的 mihomo Alpha 内核,请在组件更新中安装: $CLASH"
sleep 1 sleep 1
echo "Clashoo" > "$REAL_LOG" echo "Clashoo" > "$REAL_LOG"
return 1 return 1
@@ -2167,10 +2178,10 @@ _start_prepare_config() {
if { egrep -q '^ {0,}rule-providers:' "$CONFIG_YAML" || egrep -q '^ {0,}script:' "$CONFIG_YAML"; } && ! core_supports_advanced_features; then if { egrep -q '^ {0,}rule-providers:' "$CONFIG_YAML" || egrep -q '^ {0,}script:' "$CONFIG_YAML"; } && ! core_supports_advanced_features; then
[ "$_reload_candidate" = "1" ] && return 1 [ "$_reload_candidate" = "1" ] && return 1
if [ -x /usr/bin/mihomo ]; then if [ -n "$(_mihomo_stable_binary)" ]; then
uci set clashoo.config.core="3" && uci commit clashoo
elif [ -x /usr/bin/mihomo-stable ]; then
uci set clashoo.config.core="2" && uci commit clashoo uci set clashoo.config.core="2" && uci commit clashoo
elif [ -x /usr/share/clashoo/bin/mihomo-alpha ]; then
uci set clashoo.config.core="3" && uci commit clashoo
else else
log_msg "Current config requires mihomo/clash-meta core" "当前配置需要 mihomo 或 clash-meta 内核" log_msg "Current config requires mihomo/clash-meta core" "当前配置需要 mihomo 或 clash-meta 内核"
backend_remove_rules >/dev/null 2>&1 backend_remove_rules >/dev/null 2>&1
@@ -2407,7 +2418,6 @@ start_service(){
sync_legacy_core_field sync_legacy_core_field
_migrate_legacy_mihomo_stable _migrate_legacy_mihomo_stable
_fallback_missing_mihomo_core
CLASH="$(_detect_selected_core)" CLASH="$(_detect_selected_core)"
cleanup_legacy_startup_backups cleanup_legacy_startup_backups
@@ -1062,10 +1062,10 @@ if [ -n "$CUSTOM_CORE_URL" ]; then
write_log "使用自定义内核下载链接" write_log "使用自定义内核下载链接"
URL="$CUSTOM_CORE_URL" URL="$CUSTOM_CORE_URL"
if [ "$CORETYPE" = "3" ]; then if [ "$CORETYPE" = "3" ]; then
TARGET="/usr/bin/mihomo" TARGET="/usr/share/clashoo/bin/mihomo-alpha"
VERSION_FILE="/usr/share/clashoo/mihomo_version" VERSION_FILE="/usr/share/clashoo/mihomo_version"
else else
TARGET="/usr/bin/mihomo-stable" TARGET="/usr/share/clashoo/bin/mihomo-stable"
VERSION_FILE="/usr/share/clashoo/clash_meta_version" VERSION_FILE="/usr/share/clashoo/clash_meta_version"
fi fi
TAG="custom" TAG="custom"
@@ -1175,7 +1175,7 @@ elif [ "$CORETYPE" = "2" ]; then
[ -z "$TAG" ] && write_log "获取稳定版版本号失败" && exit 1 [ -z "$TAG" ] && write_log "获取稳定版版本号失败" && exit 1
ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "stable") ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "stable")
URL="https://github.com/MetaCubeX/mihomo/releases/download/${TAG}/${ASSET}" URL="https://github.com/MetaCubeX/mihomo/releases/download/${TAG}/${ASSET}"
TARGET="/usr/bin/mihomo-stable" TARGET="/usr/share/clashoo/bin/mihomo-stable"
VERSION_FILE="/usr/share/clashoo/clash_meta_version" VERSION_FILE="/usr/share/clashoo/clash_meta_version"
VERSION_VALUE="$TAG" VERSION_VALUE="$TAG"
else else
@@ -1189,7 +1189,7 @@ else
[ -z "$ASSET" ] && ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "alpha") [ -z "$ASSET" ] && ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "alpha")
[ -z "$ASSET" ] && ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "stable") [ -z "$ASSET" ] && ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "stable")
URL="https://github.com/MetaCubeX/mihomo/releases/download/${TAG}/${ASSET}" URL="https://github.com/MetaCubeX/mihomo/releases/download/${TAG}/${ASSET}"
TARGET="/usr/bin/mihomo" TARGET="/usr/share/clashoo/bin/mihomo-alpha"
VERSION_FILE="/usr/share/clashoo/mihomo_version" VERSION_FILE="/usr/share/clashoo/mihomo_version"
VERSION_VALUE="$(printf '%s\n' "$ASSET" | sed -n 's#^mihomo-.*-alpha-\([0-9a-fA-F]\{7,\}\)\.gz$#\1#p' | head -n 1)" VERSION_VALUE="$(printf '%s\n' "$ASSET" | sed -n 's#^mihomo-.*-alpha-\([0-9a-fA-F]\{7,\}\)\.gz$#\1#p' | head -n 1)"
[ -z "$VERSION_VALUE" ] && VERSION_VALUE="$TAG" [ -z "$VERSION_VALUE" ] && VERSION_VALUE="$TAG"
+2 -2
View File
@@ -6,9 +6,9 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=daed PKG_NAME:=daed
PKG_VERSION:=2026.10.18 PKG_VERSION:=2026.10.18
PKG_RELEASE:=62 PKG_RELEASE:=63
PKG_SOURCE:=daed-src-2026.10.18-b7827fb4b582.tar.gz PKG_SOURCE:=daed-src-2026.10.18-ac22e7a32e17.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip PKG_HASH:=skip
+2 -2
View File
@@ -9,8 +9,8 @@ include $(TOPDIR)/rules.mk
PKG_ARCH_FASTNET:=$(ARCH) PKG_ARCH_FASTNET:=$(ARCH)
PKG_NAME:=fastnet PKG_NAME:=fastnet
PKG_VERSION:=0.7.7 PKG_VERSION:=0.7.8
PKG_RELEASE:=6 PKG_RELEASE:=7
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/fastnet-runtime-v$(PKG_VERSION)/ PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/fastnet-runtime-v$(PKG_VERSION)/
+2 -2
View File
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=gately PKG_NAME:=gately
PKG_VERSION:=0.5.8 PKG_VERSION:=0.5.9
PKG_RELEASE:=11 PKG_RELEASE:=12
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)?
+2 -2
View File
@@ -11,8 +11,8 @@ PKG_ARCH_ISTOREENHANCE:=$(ARCH)
PKG_NAME:=istoreenhance PKG_NAME:=istoreenhance
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable # use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
PKG_SOURCE_DATE:=0.8.4 PKG_SOURCE_DATE:=0.8.5
PKG_RELEASE:=13 PKG_RELEASE:=14
ARCH_HEXCODE:= ARCH_HEXCODE:=
ifeq ($(ARCH),x86_64) ifeq ($(ARCH),x86_64)
ARCH_HEXCODE=8664 ARCH_HEXCODE=8664
+3 -3
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=kai-agent PKG_NAME:=kai-agent
PKG_VERSION:=0.0.26 PKG_VERSION:=0.0.27
PKG_RELEASE:=4 PKG_RELEASE:=5
PKG_SOURCE:=kai-agent-bundle-$(PKG_VERSION).tar.gz PKG_SOURCE:=kai-agent-bundle-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kai-runtime-v$(PKG_VERSION)/ PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kai-runtime-v$(PKG_VERSION)/
PKG_HASH:=skip PKG_HASH:=skip
@@ -17,7 +17,7 @@ define Package/$(PKG_NAME)
SECTION:=net SECTION:=net
CATEGORY:=System CATEGORY:=System
TITLE:=KAI OpenCode Agent bundle TITLE:=KAI OpenCode Agent bundle
URL:=https://github.com/linkease/linkease-skills URL:=https://github.com/linkease/kai-skills
endef endef
define Package/$(PKG_NAME)/description define Package/$(PKG_NAME)/description
+5 -4
View File
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
PKG_ARCH_kai:=$(ARCH) PKG_ARCH_kai:=$(ARCH)
PKG_NAME:=kai PKG_NAME:=kai
PKG_VERSION:=0.0.25 PKG_VERSION:=0.0.27
PKG_RELEASE:=9 PKG_RELEASE:=10
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kai-runtime-v$(PKG_VERSION)/ PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kai-runtime-v$(PKG_VERSION)/
PKG_HASH:=skip PKG_HASH:=skip
@@ -28,11 +28,12 @@ define Package/$(PKG_NAME)
SUBMENU:= SUBMENU:=
TITLE:=KAI TITLE:=KAI
DEPENDS:=+kai_session +kai-agent +luci-lib-linkeaseauth +linkease-app-entry DEPENDS:=+kai_session +kai-agent +luci-lib-linkeaseauth +linkease-app-entry
URL:=https://github.com/Carseason/openwrt-packages EXTRA_DEPENDS:=kai-agent (>=0.0.27~0)
URL:=https://github.com/linkease/istore-ai-helper
endef endef
define Package/$(PKG_NAME)/description define Package/$(PKG_NAME)/description
KAI is a dashboard & user guide KAI provides the OpenCode gateway and embedded Web UI with packaged local skills.
endef endef
define Package/$(PKG_NAME)/conffiles define Package/$(PKG_NAME)/conffiles
+4 -4
View File
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
PKG_ARCH_kai_session:=$(ARCH) PKG_ARCH_kai_session:=$(ARCH)
PKG_NAME:=kai_session PKG_NAME:=kai_session
PKG_VERSION:=0.0.25 PKG_VERSION:=0.0.27
PKG_RELEASE:=8 PKG_RELEASE:=9
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kai-runtime-v$(PKG_VERSION)/ PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kai-runtime-v$(PKG_VERSION)/
PKG_HASH:=skip PKG_HASH:=skip
@@ -31,11 +31,11 @@ define Package/$(PKG_NAME)
SUBMENU:= SUBMENU:=
TITLE:=KAI_SESSION TITLE:=KAI_SESSION
DEPENDS:=+libstdcpp DEPENDS:=+libstdcpp
URL:=https://github.com/Carseason/openwrt-packages URL:=https://github.com/linkease/istore-ai-helper
endef endef
define Package/$(PKG_NAME)/description define Package/$(PKG_NAME)/description
KAI The OpenCode engine and offline ripgrep runtime for KAI.
endef endef
define Package/$(PKG_NAME)/conffiles define Package/$(PKG_NAME)/conffiles
+2 -2
View File
@@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk
PKG_ARCH_KS_CLI:=$(ARCH) PKG_ARCH_KS_CLI:=$(ARCH)
PKG_NAME:=ks-cli PKG_NAME:=ks-cli
PKG_VERSION:=0.8.4 PKG_VERSION:=0.8.5
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_SOURCE:=ks-cli-binary-$(PKG_VERSION).tar.gz PKG_SOURCE:=ks-cli-binary-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/kspeeder/docker_kspeeder/releases/download/v$(PKG_VERSION)/ PKG_SOURCE_URL:=https://github.com/kspeeder/docker_kspeeder/releases/download/v$(PKG_VERSION)/
+4 -2
View File
@@ -11,8 +11,10 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
PKG_MAINTAINER:=timsaya PKG_MAINTAINER:=timsaya
PKG_VERSION:=0.12.12 PKG_VERSION:=0.12.12
PKG_RELEASE:=12 PKG_RELEASE:=13
# Rebuild language packages when a package revision changes without a PO commit.
PKG_PO_VERSION=$(if $(DUMP),x,$(strip $(call findrev)))-r$(PKG_RELEASE)
include $(TOPDIR)/feeds/luci/luci.mk include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature # call BuildPackage - OpenWrt buildroot signature
@@ -0,0 +1,227 @@
'use strict';
'require baseclass';
'require rpc';
'require ui';
'require poll';
'require request';
var callStatus = rpc.declare({ object: 'luci.bandix', method: 'getGeoipStatus', expect: {} });
var callManage = rpc.declare({ object: 'luci.bandix', method: 'manageGeoip', params: ['action', 'profile', 'enabled'], expect: {} });
function size(bytes) {
if (bytes == null) return '—';
if (bytes < 1024) return bytes + ' B';
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KiB';
return (bytes / 1048576).toFixed(1) + ' MiB';
}
function unwrap(reply) {
if (!reply || reply.status !== 'success' || !reply.data) throw new Error(reply && (reply.message || reply.error) || _('GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix.'));
return reply.data;
}
function localized(names) {
var language = (L.env.lang || document.documentElement.lang || 'en').replace(/_/g, '-');
var key = /^zh/i.test(language) ? 'zh-CN' : language.split('-')[0];
return names && (names[key] || names.en) || '';
}
function countryName(info) {
if (info.country_code && typeof Intl !== 'undefined' && Intl.DisplayNames) {
try { return new Intl.DisplayNames([(L.env.lang || 'en').replace(/_/g, '-')], { type: 'region' }).of(info.country_code); } catch (e) { /* use database names */ }
}
return localized(info.country_names) || info.country_code || '';
}
var scopes = {
local: _('Local network'), private: _('Private address'), loopback: _('Loopback'),
link_local: _('Link-local address'), shared: _('Shared address (CGNAT)'),
multicast: _('Multicast'), unspecified: _('Unspecified address'),
documentation: _('Documentation address'), reserved: _('Reserved address'), unknown: _('Unknown location')
};
var phases = {
preparing: _('Checking database version…'), checking_space: _('Checking storage…'),
downloading: _('Downloading database…'), verifying: _('Verifying database…'),
activating: _('Loading database…'), importing: _('Importing database…'),
completed: _('Database ready'), up_to_date: _('Database is up to date'),
cancelled: _('Download cancelled'), failed: _('Database operation failed')
};
return baseclass.extend({
describe: function (info) {
if (!info) return null;
if (info.scope !== 'public') return { summary: scopes[info.scope] || scopes.unknown, details: scopes[info.scope] || scopes.unknown };
var country = countryName(info), region = localized(info.region_names), city = localized(info.city_names);
var location = [country, region, city].filter(function (value, index, all) { return value && all.indexOf(value) === index; }).join(' · ');
return {
summary: [location, info.organization].filter(Boolean).join(' · ') || scopes.unknown,
details: [location, info.asn ? 'AS' + info.asn : '', info.organization].filter(Boolean).join('\n')
};
},
render: function () {
if (this._root) return this._root;
var root, current = null, pending = false, refreshing = false, dirtyProfile = false;
var badge = E('strong', {}, _('Loading…'));
var enabled = E('input', { type: 'checkbox', disabled: true });
var profile = E('select', { 'class': 'cbi-input-select', 'aria-label': _('Database contents') }, [
E('option', { value: 'country' }, _('Country/region — approximately 3.3 MiB')),
E('option', { value: 'country_asn' }, _('Country/region + network organization — approximately 12 MiB')),
E('option', { value: 'city_asn' }, _('Province/city + network organization — approximately 57 MiB'))
]);
var importedCity = null;
var details = E('div', { style: 'color:var(--text-color-secondary,#888);font-size:0.9em;line-height:1.6;overflow-wrap:anywhere' });
var message = E('div', { role: 'status', 'aria-live': 'polite', style: 'font-size:0.9em;overflow-wrap:anywhere' });
var progressFill = E('div', { style: 'width:0%' });
var progress = E('div', { 'class': 'cbi-progressbar', style: 'display:none;margin:8px 0', role: 'progressbar', 'aria-valuemin': 0, 'aria-valuemax': 100 }, progressFill);
var download = E('button', { type: 'button', 'class': 'btn cbi-button cbi-button-apply' }, _('Download and enable'));
var upload = E('button', { type: 'button', 'class': 'btn cbi-button' }, _('Upload database'));
var cancel = E('button', { type: 'button', 'class': 'btn cbi-button', style: 'display:none' }, _('Cancel'));
var remove = E('button', { type: 'button', 'class': 'btn cbi-button cbi-button-negative', style: 'display:none' }, _('Delete'));
function display(status) {
var wasBusy = current && current.job && current.job.busy;
if (wasBusy && status.job && !status.job.busy && status.job.phase === 'completed') dirtyProfile = false;
current = status;
var job = status.job || {}, databases = status.databases || [], busy = pending || job.busy;
badge.style.color = status.enabled ? 'var(--success-color,#2f9e44)' : '';
badge.textContent = status.enabled ? _('Enabled') : databases.length ? _('Disabled') : _('Not installed');
enabled.checked = !!status.enabled; enabled.disabled = busy || !databases.length;
if (!dirtyProfile && status.profile) {
if (status.profile === 'city' && !importedCity) {
importedCity = E('option', { value: 'city' }, _('Province/city (uploaded database)')); profile.appendChild(importedCity);
}
profile.value = status.profile;
}
if (job.busy && job.profile) profile.value = job.profile;
profile.disabled = busy;
download.disabled = busy; upload.disabled = busy;
download.textContent = databases.length ? _('Update database') : _('Download and enable');
remove.style.display = databases.length || status.load_error ? '' : 'none'; remove.disabled = busy;
cancel.style.display = job.busy && job.phase !== 'activating' ? '' : 'none'; cancel.disabled = pending;
var builds = databases.map(function (db) {
var name = { country: _('Country/region'), city: _('Province/city'), asn: _('Network organization') }[db.kind] || db.kind;
var date = '—';
if (db.build_epoch) {
try { date = new Date(db.build_epoch * 1000).toLocaleDateString((L.env.lang || 'en').replace(/_/g, '-')); }
catch (e) { date = new Date(db.build_epoch * 1000).toLocaleDateString(); }
}
return name + ' ' + date;
});
details.textContent = [
builds.length ? _('Version') + ': ' + builds.join(' · ') : '',
_('Storage used') + ': ' + size(status.installed_bytes) + ' · ' + _('Available space') + ': ' + size(status.free_bytes),
_('Database directory') + ': ' + status.directory
].filter(Boolean).join('\n'); details.style.whiteSpace = 'pre-line';
var total = job.total_bytes;
var percent = total > 0 ? Math.min(100, Math.floor((job.downloaded_bytes || 0) * 100 / total)) : null;
progress.style.display = job.busy && total > 0 ? '' : 'none';
progressFill.style.width = (percent || 0) + '%';
progress.title = percent == null ? '' : percent + '%';
if (percent != null) progress.setAttribute('aria-valuenow', percent);
message.style.color = job.phase === 'failed' || status.load_error ? 'var(--error-color,#c0392b)' : '';
if (status.load_error && !job.busy) {
message.textContent = _('The installed database could not be loaded. Download or upload it again.');
message.title = status.load_error;
} else if (job.phase === 'failed') {
var errorMessage = job.error_phase === 'checking_space'
? _('Cannot prepare storage. Check free space and directory permissions.')
: job.error_phase === 'verifying'
? _('Database validation failed. The previous database is unchanged.')
: _('Database operation failed. Retry or upload a local database.');
if (job.error_phase === 'checking_space' && status.free_bytes != null && job.required_bytes > status.free_bytes) {
errorMessage = _('Not enough free space for this database.') + ' ' + _('Required space') + ': ' + size(job.required_bytes) + ' · ' + _('Available space') + ': ' + size(status.free_bytes);
}
message.textContent = errorMessage; message.title = job.error || '';
} else {
message.textContent = (phases[job.phase] || '') + (job.busy && total > 0 ? ' ' + size(job.downloaded_bytes || 0) + ' / ' + size(total) + ' (' + percent + '%)' : '');
message.title = job.current_file || '';
}
}
function refresh() {
if (refreshing || (root && !root.isConnected)) return Promise.resolve();
refreshing = true;
return callStatus().then(unwrap).then(display).catch(function (error) {
message.textContent = _('GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix.');
message.title = error.message || String(error);
download.disabled = true; upload.disabled = true; enabled.disabled = true;
}).finally(function () { refreshing = false; });
}
function manage(action, selected, checked) {
pending = true; if (current) display(current);
return callManage(action, selected || '', checked ? '1' : '0').then(unwrap).then(function (status) {
if (action === 'download') dirtyProfile = true;
display(status);
}).catch(function (error) {
ui.addNotification(null, E('p', _('Database operation failed') + ': ' + (error.message || error)), 'error');
}).finally(function () { pending = false; if (current) display(current); });
}
profile.addEventListener('change', function () { dirtyProfile = true; });
enabled.addEventListener('change', function () { manage('enable', null, enabled.checked); });
download.addEventListener('click', function () { manage('download', profile.value); });
cancel.addEventListener('click', function () { manage('cancel'); });
remove.addEventListener('click', function () {
ui.showModal(_('Delete GeoIP databases'), [
E('p', _('Remove installed databases and disable IP location lookup?')),
E('div', { 'class': 'right' }, [
E('button', { type: 'button', 'class': 'btn', click: ui.hideModal }, _('Cancel')), ' ',
E('button', { type: 'button', 'class': 'btn cbi-button-negative', click: function () { ui.hideModal(); dirtyProfile = false; manage('delete'); } }, _('Delete'))
])
]);
});
upload.addEventListener('click', function () {
if (!current) return;
var limit = typeof current.upload_max_bytes === 'number' ? current.upload_max_bytes : 8 * 1048576;
var selection = E('input', { type: 'file', accept: '.mmdb', 'aria-label': _('Upload database') });
var uploadError = E('p', { style: 'color:var(--error-color,#c0392b)', role: 'alert' });
var uploadAction = E('button', { type: 'button', 'class': 'btn cbi-button-action important', disabled: true }, _('Upload and enable'));
selection.addEventListener('change', function () {
var file = selection.files[0];
uploadAction.disabled = !file || !/\.mmdb$/i.test(file.name) || file.size === 0 || file.size > limit;
uploadError.textContent = uploadAction.disabled && file ? _('Choose an uncompressed .mmdb file within the upload size limit.') : '';
});
uploadAction.addEventListener('click', function () {
var file = selection.files[0]; if (!file || uploadAction.disabled) return;
pending = true; display(current);
var fill = E('div', { style: 'width:0%' });
var bar = E('div', { 'class': 'cbi-progressbar' }, fill);
ui.showModal(_('Uploading database…'), [bar]);
var data = new FormData();
data.append('sessionid', rpc.getSessionID());
data.append('filename', '/tmp/bandix-geoip-upload.mmdb');
data.append('filedata', file);
request.post(L.env.cgi_base + '/cgi-upload', data, {
timeout: 0,
progress: function (event) {
if (!event.total) return;
var percent = Math.min(100, Math.round(event.loaded * 100 / event.total));
fill.style.width = percent + '%'; bar.title = percent + '%';
}
}).then(function (response) {
var reply = response.json();
if (!response.ok || reply.failure) throw new Error(reply.message || _('Database upload failed'));
ui.hideModal(); return manage('import');
}).catch(function (error) {
ui.hideModal(); ui.addNotification(null, E('p', _('Database upload failed') + ': ' + (error.message || error)), 'error');
}).finally(function () { pending = false; if (current) display(current); });
});
ui.showModal(_('Upload database'), [
E('p', {}, _('Maximum upload size') + ': ' + size(limit)), selection, uploadError,
E('div', { 'class': 'right' }, [E('button', { type: 'button', 'class': 'btn', click: ui.hideModal }, _('Cancel')), ' ', uploadAction])
]);
});
root = E('div', { style: 'max-width:780px' }, [
E('div', { style: 'display:flex;flex-wrap:wrap;align-items:center;gap:16px;margin-bottom:12px' }, [
E('label', { style: 'display:flex;align-items:center;gap:8px' }, [enabled, _('Enable IP location lookup')]), badge
]),
E('div', { style: 'display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px' }, [profile, download, upload, cancel, remove]),
details, progress, message,
E('div', { style: 'font-size:0.85em;opacity:0.75;margin-top:8px' }, [
_('Data source') + ': ', E('a', { href: 'https://github.com/P3TERX/GeoLite.mmdb', target: '_blank', rel: 'noopener noreferrer' }, 'P3TERX / MaxMind GeoLite2'),
' · ', E('a', { href: 'https://www.maxmind.com/en/geolite2/eula', target: '_blank', rel: 'noopener noreferrer' }, _('Data terms')),
E('div', {}, _('Upload an uncompressed Country, City or ASN .mmdb file. To add ASN, install a Country or City database first.'))
])
]);
this._root = root;
poll.add(refresh, 2);
// renderWidget returns before LuCI attaches the form to the document.
setTimeout(refresh, 0);
return root;
}
});
@@ -4,6 +4,7 @@
'require uci'; 'require uci';
'require rpc'; 'require rpc';
'require poll'; 'require poll';
'require bandix.geoip as geoip';
var BANDIX_MODAL_BG_OPENWRT2020_LIGHT = '#ffffff'; var BANDIX_MODAL_BG_OPENWRT2020_LIGHT = '#ffffff';
var BANDIX_MODAL_BG_OPENWRT2020_DARK = '#2a2a2a'; var BANDIX_MODAL_BG_OPENWRT2020_DARK = '#2a2a2a';
@@ -136,15 +137,21 @@ function formatSize(bytes) {
// IPv6 地址按 RFC 3986 用方括号包起来,一是避免 '2600:...:8000::' + ':' + '80' 拼出 // IPv6 地址按 RFC 3986 用方括号包起来,一是避免 '2600:...:8000::' + ':' + '80' 拼出
// ':::80' 这种分不清边界的写法,二是选中复制出来就是 [addr]:port,可以直接粘去用。 // ':::80' 这种分不清边界的写法,二是选中复制出来就是 [addr]:port,可以直接粘去用。
// 地址和端口是行内元素,复制时不会被拆成两行;端口自身 nowrap,不会被折断 // 地址和端口是行内元素,复制时不会被拆成两行;端口自身 nowrap,不会被折断
function flowEndpointCell(addr, port) { function flowEndpointCell(addr, port, information) {
var text = addr || '-'; var text = addr || '-';
if (text.indexOf(':') >= 0) { if (text.indexOf(':') >= 0) {
text = '[' + text + ']'; text = '[' + text + ']';
} }
return E('td', { 'class': 'flows-addr-cell' }, [ var description = geoip.describe(information);
var children = [E('div', {}, [
E('span', { 'class': 'flows-addr' }, text), E('span', { 'class': 'flows-addr' }, text),
E('span', { 'class': 'flows-port' }, port ? ':' + port : '') E('span', { 'class': 'flows-port' }, port ? ':' + port : '')
]); ])];
if (description) children.push(E('div', {
'class': 'flows-geoip', title: description.details,
style: 'font-size:0.8rem;opacity:0.75;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px'
}, description.summary));
return E('td', { 'class': 'flows-addr-cell' }, children);
} }
function formatDeviceName(device) { function formatDeviceName(device) {
@@ -1211,7 +1218,8 @@ return view.extend({
total: res.data.total || 0, total: res.data.total || 0,
page: res.data.page || currentPage, page: res.data.page || currentPage,
page_size: res.data.page_size || currentPageSize, page_size: res.data.page_size || currentPageSize,
total_pages: res.data.total_pages || 1 total_pages: res.data.total_pages || 1,
geoip: res.data.geoip || {}
}; };
} }
@@ -1233,6 +1241,7 @@ return view.extend({
tableBody.innerHTML = ''; tableBody.innerHTML = '';
var normalized = normalizeFlowsResponse(res); var normalized = normalizeFlowsResponse(res);
var flows = normalized.items; var flows = normalized.items;
var information = normalized.geoip || {};
totalItems = normalized.total; totalItems = normalized.total;
currentPage = normalized.page; currentPage = normalized.page;
totalPages = Math.max(1, normalized.total_pages); totalPages = Math.max(1, normalized.total_pages);
@@ -1258,11 +1267,11 @@ return view.extend({
tableBody.appendChild(E('tr', {}, [ tableBody.appendChild(E('tr', {}, [
E('td', { 'class': protoCls }, (f.protocol || '-').toUpperCase()), E('td', { 'class': protoCls }, (f.protocol || '-').toUpperCase()),
E('td', { 'class': 'flows-state-cell ' + stateCls }, f.state || '-'), E('td', { 'class': 'flows-state-cell ' + stateCls }, f.state || '-'),
flowEndpointCell(f.orig && f.orig.src, f.orig && f.orig.sport), flowEndpointCell(f.orig && f.orig.src, f.orig && f.orig.sport, information[f.orig && f.orig.src]),
flowEndpointCell(f.orig && f.orig.dst, f.orig && f.orig.dport), flowEndpointCell(f.orig && f.orig.dst, f.orig && f.orig.dport, information[f.orig && f.orig.dst]),
E('td', {}, sendStr), E('td', {}, sendStr),
flowEndpointCell(f.repl && f.repl.src, f.repl && f.repl.sport), flowEndpointCell(f.repl && f.repl.src, f.repl && f.repl.sport, information[f.repl && f.repl.src]),
flowEndpointCell(f.repl && f.repl.dst, f.repl && f.repl.dport), flowEndpointCell(f.repl && f.repl.dst, f.repl && f.repl.dport, information[f.repl && f.repl.dst]),
E('td', {}, replyStr), E('td', {}, replyStr),
E('td', { 'style': 'font-size: 0.8rem;' }, flagsStr) E('td', { 'style': 'font-size: 0.8rem;' }, flagsStr)
])); ]));
@@ -4,6 +4,7 @@
'require ui'; 'require ui';
'require uci'; 'require uci';
'require rpc'; 'require rpc';
'require bandix.geoip as geoip';
'require tools.widgets as widgets'; 'require tools.widgets as widgets';
@@ -231,6 +232,13 @@ return view.extend({
s.description = _('Configure basic parameters for Bandix service'); s.description = _('Configure basic parameters for Bandix service');
s.addremove = false; s.addremove = false;
o = s.option(form.Value, 'host', _('Listen Address'),
_('IP address for Bandix to listen on. Use 127.0.0.1 for local access or 0.0.0.0 / :: for all interfaces.'));
o.default = '127.0.0.1';
o.datatype = 'ipaddr("nomask")';
o.placeholder = '127.0.0.1';
o.rmempty = false;
// 添加端口设置选项 // 添加端口设置选项
o = s.option(form.Value, 'port', _('Port'), o = s.option(form.Value, 'port', _('Port'),
_('Port for Bandix service to listen on')); _('Port for Bandix service to listen on'));
@@ -785,6 +793,16 @@ return view.extend({
o.default = '0'; o.default = '0';
o.rmempty = false; o.rmempty = false;
o = s.option(form.DummyValue, '_geoip_manager', _('IP location database'));
o.renderWidget = function () { return geoip.render(); };
o = s.option(form.Value, 'geoip_dir', _('Database directory'),
_('Use persistent storage or an external drive. Save and apply directory changes before downloading.'));
o.default = '/usr/share/bandix-geoip';
o.placeholder = '/usr/share/bandix-geoip';
o.datatype = 'directory';
o.rmempty = false;
// 4. DNS监控设置部分 (dns) // 4. DNS监控设置部分 (dns)
s = m.section(form.NamedSection, 'dns', 'dns', _('DNS Monitor Settings')); s = m.section(form.NamedSection, 'dns', 'dns', _('DNS Monitor Settings'));
s.description = _('Configure DNS monitoring related parameters'); s.description = _('Configure DNS monitoring related parameters');
+189 -30
View File
@@ -221,12 +221,6 @@ msgstr "WAN cargado"
msgid "WAN Downloaded" msgid "WAN Downloaded"
msgstr "WAN descargado" msgstr "WAN descargado"
msgid "Total Upload"
msgstr "Carga total"
msgid "Total Download"
msgstr "Descarga total"
msgid "LAN Upload" msgid "LAN Upload"
msgstr "Carga LAN" msgstr "Carga LAN"
@@ -422,18 +416,12 @@ msgstr "Página"
msgid "of" msgid "of"
msgstr "de" msgstr "de"
msgid "Total"
msgstr "Total"
msgid "records" msgid "records"
msgstr "registros" msgstr "registros"
msgid "Per Page" msgid "Per Page"
msgstr "Por página" msgstr "Por página"
msgid ""
msgstr ""
msgid "Total Queries" msgid "Total Queries"
msgstr "Total de consultas" msgstr "Total de consultas"
@@ -938,18 +926,6 @@ msgstr "Máximo"
msgid "Minimum" msgid "Minimum"
msgstr "Mínimo" msgstr "Mínimo"
msgid "WAN Upload"
msgstr "Subida WAN"
msgid "WAN Download"
msgstr "Descarga WAN"
msgid "WAN Uploaded"
msgstr "Subido WAN"
msgid "WAN Downloaded"
msgstr "Descargado WAN"
msgid "Clear Traffic Data" msgid "Clear Traffic Data"
msgstr "Borrar datos de tráfico" msgstr "Borrar datos de tráfico"
@@ -1082,9 +1058,6 @@ msgstr "Descargar e Instalar"
msgid "Manual Download" msgid "Manual Download"
msgstr "Descarga Manual" msgstr "Descarga Manual"
msgid "Close"
msgstr "Cerrar"
msgid "Starting installation... The page will refresh automatically in 30 seconds." msgid "Starting installation... The page will refresh automatically in 30 seconds."
msgstr "Iniciando instalación... La página se actualizará automáticamente en 30 segundos." msgstr "Iniciando instalación... La página se actualizará automáticamente en 30 segundos."
@@ -1094,9 +1067,6 @@ msgstr "Por favor, borre manualmente la caché del navegador después de actuali
msgid "Traffic Statistics" msgid "Traffic Statistics"
msgstr "Estadísticas de Tráfico" msgstr "Estadísticas de Tráfico"
msgid "Traffic Statistics"
msgstr "Estadísticas de Tráfico"
msgid "Device Usage Ranking" msgid "Device Usage Ranking"
msgstr "Clasificación de Uso de Dispositivos" msgstr "Clasificación de Uso de Dispositivos"
@@ -1351,3 +1321,192 @@ msgstr "No disponible"
msgid "Show reply direction" msgid "Show reply direction"
msgstr "Mostrar la dirección de respuesta" msgstr "Mostrar la dirección de respuesta"
msgid "Available space"
msgstr "Espacio disponible"
msgid "Cannot prepare storage. Check free space and directory permissions."
msgstr "No se pudo preparar el almacenamiento. Compruebe el espacio y los permisos."
msgid "Checking database version…"
msgstr "Comprobando la versión…"
msgid "Checking storage…"
msgstr "Comprobando el almacenamiento…"
msgid "Choose an uncompressed .mmdb file within the upload size limit."
msgstr "Seleccione un archivo .mmdb sin comprimir dentro del límite de tamaño."
msgid "Country/region + network organization — approximately 12 MiB"
msgstr "País/región + organización de red · unos 12 MiB"
msgid "Country/region — approximately 3.3 MiB"
msgstr "País/región · unos 3,3 MiB"
msgid "Data source"
msgstr "Fuente de datos"
msgid "Data terms"
msgstr "Condiciones de uso"
msgid "Database contents"
msgstr "Contenido de la base"
msgid "Database directory"
msgstr "Directorio de la base"
msgid "Database enabled"
msgstr "Base de datos activada"
msgid "Database is up to date"
msgstr "La base está actualizada"
msgid "Database operation failed"
msgstr "Error en la operación de la base"
msgid "Database operation failed. Retry or upload a local database."
msgstr "Error en la operación. Reintente o cargue una base local."
msgid "Database upload failed"
msgstr "Error al cargar la base"
msgid "Database validation failed. The previous database is unchanged."
msgstr "Error de validación. Se conserva la base anterior."
msgid "Delete"
msgstr "Eliminar"
msgid "Delete GeoIP databases"
msgstr "Eliminar bases GeoIP"
msgid "Disabled"
msgstr "Desactivado"
msgid "Documentation address"
msgstr "Dirección de documentación"
msgid "Download and enable"
msgstr "Descargar y activar"
msgid "Download cancelled"
msgstr "Descarga cancelada"
msgid "Downloading database…"
msgstr "Descargando la base…"
msgid "Enable IP location lookup"
msgstr "Activar ubicación de IP"
msgid "Enabled"
msgstr "Activado"
msgid "GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix."
msgstr "GeoIP no está disponible. Active la monitorización de conexiones y aplique los ajustes, o actualice Bandix."
msgid "IP location database"
msgstr "Base de ubicación de IP"
msgid "Importing database…"
msgstr "Importando la base…"
msgid "Link-local address"
msgstr "Dirección de enlace local"
msgid "Loading database…"
msgstr "Cargando la base…"
msgid "Local network"
msgstr "Red local"
msgid "Loopback"
msgstr "Bucle local"
msgid "Maximum upload size"
msgstr "Tamaño máximo de carga"
msgid "Multicast"
msgstr "Multidifusión"
msgid "Not installed"
msgstr "Sin instalar"
msgid "Private address"
msgstr "Dirección privada"
msgid "Province/city (uploaded database)"
msgstr "Provincia/ciudad (base cargada)"
msgid "Province/city + network organization — approximately 57 MiB"
msgstr "Provincia/ciudad + organización de red · unos 57 MiB"
msgid "Remove installed databases and disable IP location lookup?"
msgstr "¿Eliminar las bases y desactivar la ubicación de IP?"
msgid "Reserved address"
msgstr "Dirección reservada"
msgid "Shared address (CGNAT)"
msgstr "Dirección compartida (CGNAT)"
msgid "Storage used"
msgstr "Espacio utilizado"
msgid "The installed database could not be loaded. Download or upload it again."
msgstr "No se pudo cargar la base. Descárguela o cárguela de nuevo."
msgid "Unknown"
msgstr "Desconocido"
msgid "Unknown location"
msgstr "Ubicación desconocida"
msgid "Unspecified address"
msgstr "Dirección sin especificar"
msgid "Update database"
msgstr "Actualizar base"
msgid "Upload an uncompressed Country, City or ASN .mmdb file. To add ASN, install a Country or City database first."
msgstr "Cargue un archivo Country, City o ASN .mmdb sin comprimir. Instale Country o City antes de añadir ASN."
msgid "Upload and enable"
msgstr "Cargar y activar"
msgid "Upload database"
msgstr "Cargar base"
msgid "Uploading database…"
msgstr "Subiendo la base…"
msgid "Use persistent storage or an external drive. Save and apply directory changes before downloading."
msgstr "Use almacenamiento persistente o externo. Guarde y aplique los cambios de directorio antes de descargar."
msgid "Verifying database…"
msgstr "Verificando la base…"
msgid "Version"
msgstr "Versión"
msgid "Country/region"
msgstr "País/región"
msgid "Province/city"
msgstr "Provincia/ciudad"
msgid "Network organization"
msgstr "Organización de red"
msgid "Not enough free space for this database."
msgstr "No hay espacio suficiente para esta base."
msgid "Required space"
msgstr "Espacio necesario"
msgid "Database ready"
msgstr "Base lista"
msgid "Listen Address"
msgstr "Dirección de escucha"
msgid "IP address for Bandix to listen on. Use 127.0.0.1 for local access or 0.0.0.0 / :: for all interfaces."
msgstr "Dirección IP de escucha de Bandix. Use 127.0.0.1 para acceso local o 0.0.0.0 / :: para todas las interfaces."
+189 -30
View File
@@ -221,12 +221,6 @@ msgstr "WAN envoyé"
msgid "WAN Downloaded" msgid "WAN Downloaded"
msgstr "WAN téléchargé" msgstr "WAN téléchargé"
msgid "Total Upload"
msgstr "Téléversement total"
msgid "Total Download"
msgstr "Téléchargement total"
msgid "LAN Upload" msgid "LAN Upload"
msgstr "Débit envoi LAN" msgstr "Débit envoi LAN"
@@ -422,18 +416,12 @@ msgstr "Page"
msgid "of" msgid "of"
msgstr "sur" msgstr "sur"
msgid "Total"
msgstr "Total"
msgid "records" msgid "records"
msgstr "enregistrements" msgstr "enregistrements"
msgid "Per Page" msgid "Per Page"
msgstr "Par page" msgstr "Par page"
msgid ""
msgstr ""
msgid "Total Queries" msgid "Total Queries"
msgstr "Total des requêtes" msgstr "Total des requêtes"
@@ -938,18 +926,6 @@ msgstr "Maximum"
msgid "Minimum" msgid "Minimum"
msgstr "Minimum" msgstr "Minimum"
msgid "WAN Upload"
msgstr "Envoi WAN"
msgid "WAN Download"
msgstr "Téléchargement WAN"
msgid "WAN Uploaded"
msgstr "Envoyé WAN"
msgid "WAN Downloaded"
msgstr "Téléchargé WAN"
msgid "Clear Traffic Data" msgid "Clear Traffic Data"
msgstr "Effacer les données de trafic" msgstr "Effacer les données de trafic"
@@ -1082,9 +1058,6 @@ msgstr "Télécharger et Installer"
msgid "Manual Download" msgid "Manual Download"
msgstr "Téléchargement Manuel" msgstr "Téléchargement Manuel"
msgid "Close"
msgstr "Fermer"
msgid "Starting installation... The page will refresh automatically in 30 seconds." msgid "Starting installation... The page will refresh automatically in 30 seconds."
msgstr "Démarrage de l'installation... La page sera actualisée automatiquement dans 30 secondes." msgstr "Démarrage de l'installation... La page sera actualisée automatiquement dans 30 secondes."
@@ -1094,9 +1067,6 @@ msgstr "Veuillez vider manuellement le cache du navigateur après la mise à jou
msgid "Traffic Statistics" msgid "Traffic Statistics"
msgstr "Statistiques de Trafic" msgstr "Statistiques de Trafic"
msgid "Traffic Statistics"
msgstr "Statistiques de Trafic"
msgid "Device Usage Ranking" msgid "Device Usage Ranking"
msgstr "Classement d'Utilisation des Appareils" msgstr "Classement d'Utilisation des Appareils"
@@ -1351,3 +1321,192 @@ msgstr "Indisponible"
msgid "Show reply direction" msgid "Show reply direction"
msgstr "Afficher la direction de réponse" msgstr "Afficher la direction de réponse"
msgid "Available space"
msgstr "Espace disponible"
msgid "Cannot prepare storage. Check free space and directory permissions."
msgstr "Impossible de préparer le stockage. Vérifiez l’espace libre et les permissions."
msgid "Checking database version…"
msgstr "Vérification de la version…"
msgid "Checking storage…"
msgstr "Vérification du stockage…"
msgid "Choose an uncompressed .mmdb file within the upload size limit."
msgstr "Choisissez un fichier .mmdb non compressé respectant la taille limite."
msgid "Country/region + network organization — approximately 12 MiB"
msgstr "Pays/région + organisation réseau · environ 12 MiB"
msgid "Country/region — approximately 3.3 MiB"
msgstr "Pays/région · environ 3,3 MiB"
msgid "Data source"
msgstr "Source des données"
msgid "Data terms"
msgstr "Conditions d’utilisation"
msgid "Database contents"
msgstr "Contenu de la base"
msgid "Database directory"
msgstr "Répertoire de la base"
msgid "Database enabled"
msgstr "Base activée"
msgid "Database is up to date"
msgstr "La base est à jour"
msgid "Database operation failed"
msgstr "Échec de l’opération"
msgid "Database operation failed. Retry or upload a local database."
msgstr "Échec de l’opération. Réessayez ou importez une base locale."
msgid "Database upload failed"
msgstr "Échec de l’envoi de la base"
msgid "Database validation failed. The previous database is unchanged."
msgstr "Échec de validation. La base précédente est conservée."
msgid "Delete"
msgstr "Supprimer"
msgid "Delete GeoIP databases"
msgstr "Supprimer les bases GeoIP"
msgid "Disabled"
msgstr "Désactivé"
msgid "Documentation address"
msgstr "Adresse de documentation"
msgid "Download and enable"
msgstr "Télécharger et activer"
msgid "Download cancelled"
msgstr "Téléchargement annulé"
msgid "Downloading database…"
msgstr "Téléchargement de la base…"
msgid "Enable IP location lookup"
msgstr "Activer la géolocalisation IP"
msgid "Enabled"
msgstr "Activé"
msgid "GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix."
msgstr "GeoIP indisponible. Activez le suivi des connexions et appliquez les paramètres, ou mettez Bandix à jour."
msgid "IP location database"
msgstr "Base de géolocalisation IP"
msgid "Importing database…"
msgstr "Importation de la base…"
msgid "Link-local address"
msgstr "Adresse de lien local"
msgid "Loading database…"
msgstr "Chargement de la base…"
msgid "Local network"
msgstr "Réseau local"
msgid "Loopback"
msgstr "Bouclage"
msgid "Maximum upload size"
msgstr "Taille maximale d’envoi"
msgid "Multicast"
msgstr "Multidiffusion"
msgid "Not installed"
msgstr "Non installé"
msgid "Private address"
msgstr "Adresse privée"
msgid "Province/city (uploaded database)"
msgstr "Région/ville (base importée)"
msgid "Province/city + network organization — approximately 57 MiB"
msgstr "Région/ville + organisation réseau · environ 57 MiB"
msgid "Remove installed databases and disable IP location lookup?"
msgstr "Supprimer les bases et désactiver la géolocalisation IP ?"
msgid "Reserved address"
msgstr "Adresse réservée"
msgid "Shared address (CGNAT)"
msgstr "Adresse partagée (CGNAT)"
msgid "Storage used"
msgstr "Espace utilisé"
msgid "The installed database could not be loaded. Download or upload it again."
msgstr "Impossible de charger la base. Téléchargez-la ou importez-la à nouveau."
msgid "Unknown"
msgstr "Inconnu"
msgid "Unknown location"
msgstr "Localisation inconnue"
msgid "Unspecified address"
msgstr "Adresse non spécifiée"
msgid "Update database"
msgstr "Mettre à jour la base"
msgid "Upload an uncompressed Country, City or ASN .mmdb file. To add ASN, install a Country or City database first."
msgstr "Importez un fichier Country, City ou ASN .mmdb non compressé. Installez Country ou City avant d’ajouter ASN."
msgid "Upload and enable"
msgstr "Importer et activer"
msgid "Upload database"
msgstr "Importer une base"
msgid "Uploading database…"
msgstr "Envoi de la base…"
msgid "Use persistent storage or an external drive. Save and apply directory changes before downloading."
msgstr "Utilisez un stockage persistant ou externe. Enregistrez et appliquez le changement de répertoire avant de télécharger."
msgid "Verifying database…"
msgstr "Vérification de la base…"
msgid "Version"
msgstr "Version"
msgid "Country/region"
msgstr "Pays/région"
msgid "Province/city"
msgstr "Région/ville"
msgid "Network organization"
msgstr "Organisation réseau"
msgid "Not enough free space for this database."
msgstr "Espace insuffisant pour cette base."
msgid "Required space"
msgstr "Espace nécessaire"
msgid "Database ready"
msgstr "Base prête"
msgid "Listen Address"
msgstr "Adresse d’écoute"
msgid "IP address for Bandix to listen on. Use 127.0.0.1 for local access or 0.0.0.0 / :: for all interfaces."
msgstr "Adresse IP d’écoute de Bandix. Utilisez 127.0.0.1 pour un accès local ou 0.0.0.0 / :: pour toutes les interfaces."
+189 -30
View File
@@ -221,12 +221,6 @@ msgstr "WAN アップロード済み"
msgid "WAN Downloaded" msgid "WAN Downloaded"
msgstr "WAN ダウンロード済み" msgstr "WAN ダウンロード済み"
msgid "Total Upload"
msgstr "総アップロード速度"
msgid "Total Download"
msgstr "総ダウンロード速度"
msgid "LAN Upload" msgid "LAN Upload"
msgstr "LAN アップロード速度" msgstr "LAN アップロード速度"
@@ -422,18 +416,12 @@ msgstr "ページ"
msgid "of" msgid "of"
msgstr "の" msgstr "の"
msgid "Total"
msgstr "合計"
msgid "records" msgid "records"
msgstr "件の記録" msgstr "件の記録"
msgid "Per Page" msgid "Per Page"
msgstr "ページあたり" msgstr "ページあたり"
msgid ""
msgstr "件"
msgid "Total Queries" msgid "Total Queries"
msgstr "総クエリ数" msgstr "総クエリ数"
@@ -938,18 +926,6 @@ msgstr "最大"
msgid "Minimum" msgid "Minimum"
msgstr "最小" msgstr "最小"
msgid "WAN Upload"
msgstr "WAN アップロード"
msgid "WAN Download"
msgstr "WAN ダウンロード"
msgid "WAN Uploaded"
msgstr "WAN アップロード量"
msgid "WAN Downloaded"
msgstr "WAN ダウンロード量"
msgid "Clear Traffic Data" msgid "Clear Traffic Data"
msgstr "トラフィックデータを削除" msgstr "トラフィックデータを削除"
@@ -1082,9 +1058,6 @@ msgstr "ダウンロードしてインストール"
msgid "Manual Download" msgid "Manual Download"
msgstr "手動ダウンロード" msgstr "手動ダウンロード"
msgid "Close"
msgstr "閉じる"
msgid "Starting installation... The page will refresh automatically in 30 seconds." msgid "Starting installation... The page will refresh automatically in 30 seconds."
msgstr "インストールを開始しています... ページは30秒後に自動的に更新されます。" msgstr "インストールを開始しています... ページは30秒後に自動的に更新されます。"
@@ -1094,9 +1067,6 @@ msgstr "更新後、ブラウザのキャッシュを手動でクリアしてく
msgid "Traffic Statistics" msgid "Traffic Statistics"
msgstr "トラフィック統計" msgstr "トラフィック統計"
msgid "Traffic Statistics"
msgstr "トラフィック統計"
msgid "Device Usage Ranking" msgid "Device Usage Ranking"
msgstr "デバイス使用量ランキング" msgstr "デバイス使用量ランキング"
@@ -1351,3 +1321,192 @@ msgstr "利用不可"
msgid "Show reply direction" msgid "Show reply direction"
msgstr "応答方向を表示" msgstr "応答方向を表示"
msgid "Available space"
msgstr "空き容量"
msgid "Cannot prepare storage. Check free space and directory permissions."
msgstr "保存先を準備できません。空き容量と権限を確認してください。"
msgid "Checking database version…"
msgstr "データベースのバージョンを確認中…"
msgid "Checking storage…"
msgstr "空き容量を確認中…"
msgid "Choose an uncompressed .mmdb file within the upload size limit."
msgstr "上限サイズ以内の未圧縮 .mmdb ファイルを選択してください。"
msgid "Country/region + network organization — approximately 12 MiB"
msgstr "国・地域 + ネットワーク組織 · 約 12 MiB"
msgid "Country/region — approximately 3.3 MiB"
msgstr "国・地域 · 約 3.3 MiB"
msgid "Data source"
msgstr "データ提供元"
msgid "Data terms"
msgstr "データ利用規約"
msgid "Database contents"
msgstr "データベースの内容"
msgid "Database directory"
msgstr "データベース保存先"
msgid "Database enabled"
msgstr "データベースを有効にしました"
msgid "Database is up to date"
msgstr "データベースは最新です"
msgid "Database operation failed"
msgstr "データベース操作に失敗しました"
msgid "Database operation failed. Retry or upload a local database."
msgstr "操作に失敗しました。再試行するかローカルのデータベースをアップロードしてください。"
msgid "Database upload failed"
msgstr "データベースのアップロードに失敗しました"
msgid "Database validation failed. The previous database is unchanged."
msgstr "検証に失敗しました。既存のデータベースは保持されています。"
msgid "Delete"
msgstr "削除"
msgid "Delete GeoIP databases"
msgstr "GeoIP データベースを削除"
msgid "Disabled"
msgstr "無効"
msgid "Documentation address"
msgstr "文書用アドレス"
msgid "Download and enable"
msgstr "ダウンロードして有効化"
msgid "Download cancelled"
msgstr "ダウンロードを中止しました"
msgid "Downloading database…"
msgstr "データベースをダウンロード中…"
msgid "Enable IP location lookup"
msgstr "IP 位置情報の検索を有効化"
msgid "Enabled"
msgstr "有効"
msgid "GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix."
msgstr "GeoIP を利用できません。接続監視を有効にして設定を適用するか、Bandix を更新してください。"
msgid "IP location database"
msgstr "IP 位置情報データベース"
msgid "Importing database…"
msgstr "データベースをインポート中…"
msgid "Link-local address"
msgstr "リンクローカルアドレス"
msgid "Loading database…"
msgstr "データベースを読み込み中…"
msgid "Local network"
msgstr "ローカルネットワーク"
msgid "Loopback"
msgstr "ループバック"
msgid "Maximum upload size"
msgstr "最大アップロードサイズ"
msgid "Multicast"
msgstr "マルチキャスト"
msgid "Not installed"
msgstr "未インストール"
msgid "Private address"
msgstr "プライベートアドレス"
msgid "Province/city (uploaded database)"
msgstr "都道府県・都市(アップロード済み)"
msgid "Province/city + network organization — approximately 57 MiB"
msgstr "都道府県・都市 + ネットワーク組織 · 約 57 MiB"
msgid "Remove installed databases and disable IP location lookup?"
msgstr "データベースを削除し、IP 位置情報の検索を無効にしますか?"
msgid "Reserved address"
msgstr "予約済みアドレス"
msgid "Shared address (CGNAT)"
msgstr "共有アドレス(CGNAT)"
msgid "Storage used"
msgstr "使用容量"
msgid "The installed database could not be loaded. Download or upload it again."
msgstr "データベースを読み込めません。再度ダウンロードまたはアップロードしてください。"
msgid "Unknown"
msgstr "不明"
msgid "Unknown location"
msgstr "位置情報不明"
msgid "Unspecified address"
msgstr "未指定アドレス"
msgid "Update database"
msgstr "データベースを更新"
msgid "Upload an uncompressed Country, City or ASN .mmdb file. To add ASN, install a Country or City database first."
msgstr "未圧縮の Country、City、ASN の .mmdb ファイルをアップロードできます。ASN の追加前に Country または City をインストールしてください。"
msgid "Upload and enable"
msgstr "アップロードして有効化"
msgid "Upload database"
msgstr "データベースをアップロード"
msgid "Uploading database…"
msgstr "データベースをアップロード中…"
msgid "Use persistent storage or an external drive. Save and apply directory changes before downloading."
msgstr "永続ストレージまたは外付けドライブを使用してください。保存先の変更を保存・適用してからダウンロードしてください。"
msgid "Verifying database…"
msgstr "データベースを検証中…"
msgid "Version"
msgstr "バージョン"
msgid "Country/region"
msgstr "国・地域"
msgid "Province/city"
msgstr "都道府県・都市"
msgid "Network organization"
msgstr "ネットワーク組織"
msgid "Not enough free space for this database."
msgstr "このデータベースを保存する空き容量が不足しています。"
msgid "Required space"
msgstr "必要な容量"
msgid "Database ready"
msgstr "データベースの準備完了"
msgid "Listen Address"
msgstr "待ち受けアドレス"
msgid "IP address for Bandix to listen on. Use 127.0.0.1 for local access or 0.0.0.0 / :: for all interfaces."
msgstr "Bandix が待ち受ける IP アドレス。ローカルアクセスには 127.0.0.1、すべてのインターフェースには 0.0.0.0 / :: を指定します。"
+189 -30
View File
@@ -221,12 +221,6 @@ msgstr "WAN wysłane"
msgid "WAN Downloaded" msgid "WAN Downloaded"
msgstr "WAN pobrane" msgstr "WAN pobrane"
msgid "Total Upload"
msgstr "Całkowite wysyłanie"
msgid "Total Download"
msgstr "Całkowite pobieranie"
msgid "LAN Upload" msgid "LAN Upload"
msgstr "Wysyłanie LAN" msgstr "Wysyłanie LAN"
@@ -422,18 +416,12 @@ msgstr "Strona"
msgid "of" msgid "of"
msgstr "z" msgstr "z"
msgid "Total"
msgstr "Całkowity"
msgid "records" msgid "records"
msgstr "rekordów" msgstr "rekordów"
msgid "Per Page" msgid "Per Page"
msgstr "Na stronę" msgstr "Na stronę"
msgid ""
msgstr ""
msgid "Total Queries" msgid "Total Queries"
msgstr "Całkowite zapytania" msgstr "Całkowite zapytania"
@@ -938,18 +926,6 @@ msgstr "Maksimum"
msgid "Minimum" msgid "Minimum"
msgstr "Minimum" msgstr "Minimum"
msgid "WAN Upload"
msgstr "Wysyłanie WAN"
msgid "WAN Download"
msgstr "Pobieranie WAN"
msgid "WAN Uploaded"
msgstr "Wysłano WAN"
msgid "WAN Downloaded"
msgstr "Pobrano WAN"
msgid "Clear Traffic Data" msgid "Clear Traffic Data"
msgstr "Wyczyść dane ruchu" msgstr "Wyczyść dane ruchu"
@@ -1082,9 +1058,6 @@ msgstr "Pobierz i Zainstaluj"
msgid "Manual Download" msgid "Manual Download"
msgstr "Pobieranie Ręczne" msgstr "Pobieranie Ręczne"
msgid "Close"
msgstr "Zamknij"
msgid "Starting installation... The page will refresh automatically in 30 seconds." msgid "Starting installation... The page will refresh automatically in 30 seconds."
msgstr "Rozpoczynanie instalacji... Strona odświeży się automatycznie za 30 sekund." msgstr "Rozpoczynanie instalacji... Strona odświeży się automatycznie za 30 sekund."
@@ -1094,9 +1067,6 @@ msgstr "Po aktualizacji należy ręcznie wyczyścić pamięć podręczną przegl
msgid "Traffic Statistics" msgid "Traffic Statistics"
msgstr "Statystyki ruchu" msgstr "Statystyki ruchu"
msgid "Traffic Statistics"
msgstr "Statystyki ruchu"
msgid "Device Usage Ranking" msgid "Device Usage Ranking"
msgstr "Ranking Użycia Urządzeń" msgstr "Ranking Użycia Urządzeń"
@@ -1351,3 +1321,192 @@ msgstr "Niedostępne"
msgid "Show reply direction" msgid "Show reply direction"
msgstr "Pokaż kierunek odpowiedzi" msgstr "Pokaż kierunek odpowiedzi"
msgid "Available space"
msgstr "Wolne miejsce"
msgid "Cannot prepare storage. Check free space and directory permissions."
msgstr "Nie można przygotować pamięci. Sprawdź wolne miejsce i uprawnienia katalogu."
msgid "Checking database version…"
msgstr "Sprawdzanie wersji bazy…"
msgid "Checking storage…"
msgstr "Sprawdzanie pamięci…"
msgid "Choose an uncompressed .mmdb file within the upload size limit."
msgstr "Wybierz nieskompresowany plik .mmdb mieszczący się w limicie rozmiaru."
msgid "Country/region + network organization — approximately 12 MiB"
msgstr "Kraj/region + organizacja sieciowa · ok. 12 MiB"
msgid "Country/region — approximately 3.3 MiB"
msgstr "Kraj/region · ok. 3,3 MiB"
msgid "Data source"
msgstr "Źródło danych"
msgid "Data terms"
msgstr "Warunki korzystania"
msgid "Database contents"
msgstr "Zawartość bazy"
msgid "Database directory"
msgstr "Katalog bazy"
msgid "Database enabled"
msgstr "Baza danych włączona"
msgid "Database is up to date"
msgstr "Baza danych jest aktualna"
msgid "Database operation failed"
msgstr "Operacja na bazie nie powiodła się"
msgid "Database operation failed. Retry or upload a local database."
msgstr "Operacja nie powiodła się. Spróbuj ponownie lub prześlij lokalną bazę."
msgid "Database upload failed"
msgstr "Przesyłanie bazy nie powiodło się"
msgid "Database validation failed. The previous database is unchanged."
msgstr "Weryfikacja nie powiodła się. Poprzednia baza pozostaje bez zmian."
msgid "Delete"
msgstr "Usuń"
msgid "Delete GeoIP databases"
msgstr "Usuń bazy GeoIP"
msgid "Disabled"
msgstr "Wyłączone"
msgid "Documentation address"
msgstr "Adres dokumentacyjny"
msgid "Download and enable"
msgstr "Pobierz i włącz"
msgid "Download cancelled"
msgstr "Pobieranie anulowane"
msgid "Downloading database…"
msgstr "Pobieranie bazy…"
msgid "Enable IP location lookup"
msgstr "Włącz geolokalizację IP"
msgid "Enabled"
msgstr "Włączone"
msgid "GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix."
msgstr "GeoIP jest niedostępne. Włącz monitorowanie połączeń i zastosuj ustawienia lub zaktualizuj Bandix."
msgid "IP location database"
msgstr "Baza geolokalizacji IP"
msgid "Importing database…"
msgstr "Importowanie bazy…"
msgid "Link-local address"
msgstr "Adres lokalny łącza"
msgid "Loading database…"
msgstr "Wczytywanie bazy…"
msgid "Local network"
msgstr "Sieć lokalna"
msgid "Loopback"
msgstr "Pętla zwrotna"
msgid "Maximum upload size"
msgstr "Maksymalny rozmiar pliku"
msgid "Multicast"
msgstr "Multicast"
msgid "Not installed"
msgstr "Niezainstalowane"
msgid "Private address"
msgstr "Adres prywatny"
msgid "Province/city (uploaded database)"
msgstr "Region/miasto (przesłana baza)"
msgid "Province/city + network organization — approximately 57 MiB"
msgstr "Region/miasto + organizacja sieciowa · ok. 57 MiB"
msgid "Remove installed databases and disable IP location lookup?"
msgstr "Usunąć bazy i wyłączyć geolokalizację IP?"
msgid "Reserved address"
msgstr "Adres zarezerwowany"
msgid "Shared address (CGNAT)"
msgstr "Adres współdzielony (CGNAT)"
msgid "Storage used"
msgstr "Zajęte miejsce"
msgid "The installed database could not be loaded. Download or upload it again."
msgstr "Nie można wczytać bazy. Pobierz lub prześlij ją ponownie."
msgid "Unknown"
msgstr "Nieznane"
msgid "Unknown location"
msgstr "Nieznana lokalizacja"
msgid "Unspecified address"
msgstr "Adres nieokreślony"
msgid "Update database"
msgstr "Aktualizuj bazę"
msgid "Upload an uncompressed Country, City or ASN .mmdb file. To add ASN, install a Country or City database first."
msgstr "Prześlij nieskompresowany plik Country, City lub ASN .mmdb. Przed ASN zainstaluj Country lub City."
msgid "Upload and enable"
msgstr "Prześlij i włącz"
msgid "Upload database"
msgstr "Prześlij bazę"
msgid "Uploading database…"
msgstr "Przesyłanie bazy…"
msgid "Use persistent storage or an external drive. Save and apply directory changes before downloading."
msgstr "Użyj pamięci trwałej lub dysku zewnętrznego. Zapisz i zastosuj zmianę katalogu przed pobieraniem."
msgid "Verifying database…"
msgstr "Weryfikacja bazy…"
msgid "Version"
msgstr "Wersja"
msgid "Country/region"
msgstr "Kraj/region"
msgid "Province/city"
msgstr "Region/miasto"
msgid "Network organization"
msgstr "Organizacja sieciowa"
msgid "Not enough free space for this database."
msgstr "Brak miejsca na tę bazę danych."
msgid "Required space"
msgstr "Wymagane miejsce"
msgid "Database ready"
msgstr "Baza gotowa"
msgid "Listen Address"
msgstr "Adres nasłuchiwania"
msgid "IP address for Bandix to listen on. Use 127.0.0.1 for local access or 0.0.0.0 / :: for all interfaces."
msgstr "Adres IP nasłuchiwania Bandix. Użyj 127.0.0.1 dla dostępu lokalnego lub 0.0.0.0 / :: dla wszystkich interfejsów."
+189 -30
View File
@@ -221,12 +221,6 @@ msgstr "WAN отправлено"
msgid "WAN Downloaded" msgid "WAN Downloaded"
msgstr "WAN загружено" msgstr "WAN загружено"
msgid "Total Upload"
msgstr "Всего выгружено"
msgid "Total Download"
msgstr "Всего загружено"
msgid "LAN Upload" msgid "LAN Upload"
msgstr "Скорость отправки LAN" msgstr "Скорость отправки LAN"
@@ -422,18 +416,12 @@ msgstr "Страница"
msgid "of" msgid "of"
msgstr "из" msgstr "из"
msgid "Total"
msgstr "Всего"
msgid "records" msgid "records"
msgstr "записей" msgstr "записей"
msgid "Per Page" msgid "Per Page"
msgstr "На странице" msgstr "На странице"
msgid ""
msgstr ""
msgid "Total Queries" msgid "Total Queries"
msgstr "Всего запросов" msgstr "Всего запросов"
@@ -938,18 +926,6 @@ msgstr "Максимум"
msgid "Minimum" msgid "Minimum"
msgstr "Минимум" msgstr "Минимум"
msgid "WAN Upload"
msgstr "Отправка WAN"
msgid "WAN Download"
msgstr "Загрузка WAN"
msgid "WAN Uploaded"
msgstr "Отправлено WAN"
msgid "WAN Downloaded"
msgstr "Загружено WAN"
msgid "Clear Traffic Data" msgid "Clear Traffic Data"
msgstr "Очистить данные трафика" msgstr "Очистить данные трафика"
@@ -1082,9 +1058,6 @@ msgstr "Скачать и Установить"
msgid "Manual Download" msgid "Manual Download"
msgstr "Ручная Загрузка" msgstr "Ручная Загрузка"
msgid "Close"
msgstr "Закрыть"
msgid "Starting installation... The page will refresh automatically in 30 seconds." msgid "Starting installation... The page will refresh automatically in 30 seconds."
msgstr "Начало установки... Страница автоматически обновится через 30 секунд." msgstr "Начало установки... Страница автоматически обновится через 30 секунд."
@@ -1094,9 +1067,6 @@ msgstr "Пожалуйста, вручную очистите кеш брауз
msgid "Traffic Statistics" msgid "Traffic Statistics"
msgstr "Статистика трафика" msgstr "Статистика трафика"
msgid "Traffic Statistics"
msgstr "Статистика трафика"
msgid "Device Usage Ranking" msgid "Device Usage Ranking"
msgstr "Рейтинг использования устройств" msgstr "Рейтинг использования устройств"
@@ -1351,3 +1321,192 @@ msgstr "Недоступно"
msgid "Show reply direction" msgid "Show reply direction"
msgstr "Показывать обратное направление" msgstr "Показывать обратное направление"
msgid "Available space"
msgstr "Свободное место"
msgid "Cannot prepare storage. Check free space and directory permissions."
msgstr "Не удалось подготовить хранилище. Проверьте свободное место и права доступа."
msgid "Checking database version…"
msgstr "Проверка версии базы…"
msgid "Checking storage…"
msgstr "Проверка хранилища…"
msgid "Choose an uncompressed .mmdb file within the upload size limit."
msgstr "Выберите несжатый файл .mmdb, не превышающий ограничение размера."
msgid "Country/region + network organization — approximately 12 MiB"
msgstr "Страна/регион + сетевая организация · около 12 MiB"
msgid "Country/region — approximately 3.3 MiB"
msgstr "Страна/регион · около 3,3 MiB"
msgid "Data source"
msgstr "Источник данных"
msgid "Data terms"
msgstr "Условия использования"
msgid "Database contents"
msgstr "Содержимое базы"
msgid "Database directory"
msgstr "Каталог базы"
msgid "Database enabled"
msgstr "База данных включена"
msgid "Database is up to date"
msgstr "База данных актуальна"
msgid "Database operation failed"
msgstr "Ошибка операции с базой"
msgid "Database operation failed. Retry or upload a local database."
msgstr "Операция не удалась. Повторите попытку или загрузите локальную базу."
msgid "Database upload failed"
msgstr "Не удалось загрузить базу"
msgid "Database validation failed. The previous database is unchanged."
msgstr "Проверка не пройдена. Предыдущая база сохранена."
msgid "Delete"
msgstr "Удалить"
msgid "Delete GeoIP databases"
msgstr "Удалить базы GeoIP"
msgid "Disabled"
msgstr "Отключено"
msgid "Documentation address"
msgstr "Адрес для документации"
msgid "Download and enable"
msgstr "Скачать и включить"
msgid "Download cancelled"
msgstr "Загрузка отменена"
msgid "Downloading database…"
msgstr "Скачивание базы…"
msgid "Enable IP location lookup"
msgstr "Включить геолокацию IP"
msgid "Enabled"
msgstr "Включено"
msgid "GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix."
msgstr "GeoIP недоступен. Включите мониторинг соединений и примените настройки либо обновите Bandix."
msgid "IP location database"
msgstr "База геолокации IP"
msgid "Importing database…"
msgstr "Импорт базы…"
msgid "Link-local address"
msgstr "Локальный адрес канала"
msgid "Loading database…"
msgstr "Подключение базы…"
msgid "Local network"
msgstr "Локальная сеть"
msgid "Loopback"
msgstr "Петлевой адрес"
msgid "Maximum upload size"
msgstr "Максимальный размер загрузки"
msgid "Multicast"
msgstr "Многоадресная рассылка"
msgid "Not installed"
msgstr "Не установлено"
msgid "Private address"
msgstr "Частный адрес"
msgid "Province/city (uploaded database)"
msgstr "Регион/город (загруженная база)"
msgid "Province/city + network organization — approximately 57 MiB"
msgstr "Регион/город + сетевая организация · около 57 MiB"
msgid "Remove installed databases and disable IP location lookup?"
msgstr "Удалить базы и отключить геолокацию IP?"
msgid "Reserved address"
msgstr "Зарезервированный адрес"
msgid "Shared address (CGNAT)"
msgstr "Общий адрес (CGNAT)"
msgid "Storage used"
msgstr "Занято места"
msgid "The installed database could not be loaded. Download or upload it again."
msgstr "Не удалось открыть базу. Скачайте или загрузите её повторно."
msgid "Unknown"
msgstr "Неизвестно"
msgid "Unknown location"
msgstr "Местоположение неизвестно"
msgid "Unspecified address"
msgstr "Неуказанный адрес"
msgid "Update database"
msgstr "Обновить базу"
msgid "Upload an uncompressed Country, City or ASN .mmdb file. To add ASN, install a Country or City database first."
msgstr "Можно загрузить несжатый файл Country, City или ASN .mmdb. Перед ASN установите Country или City."
msgid "Upload and enable"
msgstr "Загрузить и включить"
msgid "Upload database"
msgstr "Загрузить базу"
msgid "Uploading database…"
msgstr "Загрузка базы…"
msgid "Use persistent storage or an external drive. Save and apply directory changes before downloading."
msgstr "Используйте постоянное хранилище или внешний диск. Перед скачиванием сохраните и примените изменение каталога."
msgid "Verifying database…"
msgstr "Проверка базы…"
msgid "Version"
msgstr "Версия"
msgid "Country/region"
msgstr "Страна/регион"
msgid "Province/city"
msgstr "Регион/город"
msgid "Network organization"
msgstr "Сетевая организация"
msgid "Not enough free space for this database."
msgstr "Недостаточно места для этой базы."
msgid "Required space"
msgstr "Требуемое место"
msgid "Database ready"
msgstr "База готова"
msgid "Listen Address"
msgstr "Адрес прослушивания"
msgid "IP address for Bandix to listen on. Use 127.0.0.1 for local access or 0.0.0.0 / :: for all interfaces."
msgstr "IP-адрес прослушивания Bandix. Используйте 127.0.0.1 для локального доступа или 0.0.0.0 / :: для всех интерфейсов."
+190 -31
View File
@@ -221,12 +221,6 @@ msgstr "WAN 已上传"
msgid "WAN Downloaded" msgid "WAN Downloaded"
msgstr "WAN 已下载" msgstr "WAN 已下载"
msgid "Total Upload"
msgstr "总上传速率"
msgid "Total Download"
msgstr "总下载速率"
msgid "LAN Upload" msgid "LAN Upload"
msgstr "LAN 上传速率" msgstr "LAN 上传速率"
@@ -419,18 +413,12 @@ msgstr "第"
msgid "of" msgid "of"
msgstr "页,共" msgstr "页,共"
msgid "Total"
msgstr "全部"
msgid "records" msgid "records"
msgstr "条记录" msgstr "条记录"
msgid "Per Page" msgid "Per Page"
msgstr "每页显示" msgstr "每页显示"
msgid ""
msgstr "条"
msgid "Total Queries" msgid "Total Queries"
msgstr "总查询数" msgstr "总查询数"
@@ -852,7 +840,7 @@ msgid "Please select at least one day"
msgstr "请至少选择一天" msgstr "请至少选择一天"
msgid "No scheduled rules yet, click \"Add Rule\" to start setting" msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
msgstr "还没有定时限速规则,点击"添加规则"开始设置" msgstr "还没有定时限速规则,点击\"添加规则\"开始设置"
msgid "Loading..." msgid "Loading..."
msgstr "加载中..." msgstr "加载中..."
@@ -935,18 +923,6 @@ msgstr "最大值"
msgid "Minimum" msgid "Minimum"
msgstr "最小值" msgstr "最小值"
msgid "WAN Upload"
msgstr "WAN 上传"
msgid "WAN Download"
msgstr "WAN 下载"
msgid "WAN Uploaded"
msgstr "WAN 上传量"
msgid "WAN Downloaded"
msgstr "WAN 下载量"
msgid "Clear Traffic Data" msgid "Clear Traffic Data"
msgstr "删除流量数据" msgstr "删除流量数据"
@@ -1079,9 +1055,6 @@ msgstr "下载并安装"
msgid "Manual Download" msgid "Manual Download"
msgstr "手动下载" msgstr "手动下载"
msgid "Close"
msgstr "关闭"
msgid "Starting installation... The page will refresh automatically in 30 seconds." msgid "Starting installation... The page will refresh automatically in 30 seconds."
msgstr "开始安装... 页面将在30秒后自动刷新。" msgstr "开始安装... 页面将在30秒后自动刷新。"
@@ -1097,9 +1070,6 @@ msgstr "有更新,点击前往设置"
msgid "Traffic Statistics" msgid "Traffic Statistics"
msgstr "流量统计" msgstr "流量统计"
msgid "Traffic Statistics"
msgstr "流量统计"
msgid "Device Usage Ranking" msgid "Device Usage Ranking"
msgstr "设备用量排行" msgstr "设备用量排行"
@@ -1378,3 +1348,192 @@ msgstr "每分钟"
msgid "Show reply direction" msgid "Show reply direction"
msgstr "显示回复方向" msgstr "显示回复方向"
msgid "Available space"
msgstr "可用空间"
msgid "Cannot prepare storage. Check free space and directory permissions."
msgstr "无法准备存储,请检查剩余空间和目录权限。"
msgid "Checking database version…"
msgstr "正在检查数据库版本…"
msgid "Checking storage…"
msgstr "正在检查存储空间…"
msgid "Choose an uncompressed .mmdb file within the upload size limit."
msgstr "请选择未压缩且未超过上传大小限制的 .mmdb 文件。"
msgid "Country/region + network organization — approximately 12 MiB"
msgstr "国家/地区 + 网络组织 · 约 12 MiB"
msgid "Country/region — approximately 3.3 MiB"
msgstr "国家/地区 · 约 3.3 MiB"
msgid "Data source"
msgstr "数据来源"
msgid "Data terms"
msgstr "数据使用条款"
msgid "Database contents"
msgstr "数据库内容"
msgid "Database directory"
msgstr "数据库目录"
msgid "Database enabled"
msgstr "数据库已启用"
msgid "Database is up to date"
msgstr "数据库已是最新版本"
msgid "Database operation failed"
msgstr "数据库操作失败"
msgid "Database operation failed. Retry or upload a local database."
msgstr "数据库操作失败,请重试或上传本地数据库。"
msgid "Database upload failed"
msgstr "数据库上传失败"
msgid "Database validation failed. The previous database is unchanged."
msgstr "数据库校验失败,原数据库未更改。"
msgid "Delete"
msgstr "删除"
msgid "Delete GeoIP databases"
msgstr "删除 IP 归属地数据库"
msgid "Disabled"
msgstr "已停用"
msgid "Documentation address"
msgstr "文档示例地址"
msgid "Download and enable"
msgstr "下载并启用"
msgid "Download cancelled"
msgstr "下载已取消"
msgid "Downloading database…"
msgstr "正在下载数据库…"
msgid "Enable IP location lookup"
msgstr "启用 IP 归属地查询"
msgid "Enabled"
msgstr "已启用"
msgid "GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix."
msgstr "IP 归属地服务不可用,请启用连接监控并应用设置,或更新 Bandix。"
msgid "IP location database"
msgstr "IP 归属地数据库"
msgid "Importing database…"
msgstr "正在导入数据库…"
msgid "Link-local address"
msgstr "链路本地地址"
msgid "Loading database…"
msgstr "正在加载数据库…"
msgid "Local network"
msgstr "局域网"
msgid "Loopback"
msgstr "回环地址"
msgid "Maximum upload size"
msgstr "上传大小上限"
msgid "Multicast"
msgstr "组播地址"
msgid "Not installed"
msgstr "未安装"
msgid "Private address"
msgstr "私有地址"
msgid "Province/city (uploaded database)"
msgstr "省市(已上传的数据库)"
msgid "Province/city + network organization — approximately 57 MiB"
msgstr "省市 + 网络组织 · 约 57 MiB"
msgid "Remove installed databases and disable IP location lookup?"
msgstr "删除已安装的数据库并停用 IP 归属地查询?"
msgid "Reserved address"
msgstr "保留地址"
msgid "Shared address (CGNAT)"
msgstr "共享地址(CGNAT)"
msgid "Storage used"
msgstr "存储占用"
msgid "The installed database could not be loaded. Download or upload it again."
msgstr "无法加载已安装的数据库,请重新下载或上传。"
msgid "Unknown"
msgstr "未知"
msgid "Unknown location"
msgstr "归属地未知"
msgid "Unspecified address"
msgstr "未指定地址"
msgid "Update database"
msgstr "更新数据库"
msgid "Upload an uncompressed Country, City or ASN .mmdb file. To add ASN, install a Country or City database first."
msgstr "可上传未压缩的 Country、City 或 ASN .mmdb 文件。添加 ASN 前,请先安装 Country 或 City 数据库。"
msgid "Upload and enable"
msgstr "上传并启用"
msgid "Upload database"
msgstr "上传数据库"
msgid "Uploading database…"
msgstr "正在上传数据库…"
msgid "Use persistent storage or an external drive. Save and apply directory changes before downloading."
msgstr "请使用持久存储或外接磁盘。更改目录后,请先保存并应用,再下载数据库。"
msgid "Verifying database…"
msgstr "正在校验数据库…"
msgid "Version"
msgstr "版本"
msgid "Country/region"
msgstr "国家/地区"
msgid "Province/city"
msgstr "省市"
msgid "Network organization"
msgstr "网络组织"
msgid "Not enough free space for this database."
msgstr "没有足够空间安装此数据库。"
msgid "Required space"
msgstr "所需空间"
msgid "Database ready"
msgstr "数据库已就绪"
msgid "Listen Address"
msgstr "监听地址"
msgid "IP address for Bandix to listen on. Use 127.0.0.1 for local access or 0.0.0.0 / :: for all interfaces."
msgstr "Bandix 监听的 IP 地址。127.0.0.1 仅供本机访问,0.0.0.0 / :: 监听所有接口。"
+190 -31
View File
@@ -221,12 +221,6 @@ msgstr "WAN 已上傳"
msgid "WAN Downloaded" msgid "WAN Downloaded"
msgstr "WAN 已下載" msgstr "WAN 已下載"
msgid "Total Upload"
msgstr "總上傳速率"
msgid "Total Download"
msgstr "總下載速率"
msgid "LAN Upload" msgid "LAN Upload"
msgstr "LAN 上傳速率" msgstr "LAN 上傳速率"
@@ -422,18 +416,12 @@ msgstr "第"
msgid "of" msgid "of"
msgstr "頁,共" msgstr "頁,共"
msgid "Total"
msgstr "全部"
msgid "records" msgid "records"
msgstr "筆記錄" msgstr "筆記錄"
msgid "Per Page" msgid "Per Page"
msgstr "每頁顯示" msgstr "每頁顯示"
msgid ""
msgstr "筆"
msgid "Total Queries" msgid "Total Queries"
msgstr "總查詢數" msgstr "總查詢數"
@@ -855,7 +843,7 @@ msgid "Please select at least one day"
msgstr "請至少選擇一天" msgstr "請至少選擇一天"
msgid "No scheduled rules yet, click \"Add Rule\" to start setting" msgid "No scheduled rules yet, click \"Add Rule\" to start setting"
msgstr "還沒有定時限速規則,點擊"新增規則"開始設定" msgstr "還沒有定時限速規則,點擊\"新增規則\"開始設定"
msgid "Loading..." msgid "Loading..."
msgstr "載入中..." msgstr "載入中..."
@@ -938,18 +926,6 @@ msgstr "最大值"
msgid "Minimum" msgid "Minimum"
msgstr "最小值" msgstr "最小值"
msgid "WAN Upload"
msgstr "WAN 上傳"
msgid "WAN Download"
msgstr "WAN 下載"
msgid "WAN Uploaded"
msgstr "WAN 上傳量"
msgid "WAN Downloaded"
msgstr "WAN 下載量"
msgid "Clear Traffic Data" msgid "Clear Traffic Data"
msgstr "刪除流量數據" msgstr "刪除流量數據"
@@ -1082,9 +1058,6 @@ msgstr "下載並安裝"
msgid "Manual Download" msgid "Manual Download"
msgstr "手動下載" msgstr "手動下載"
msgid "Close"
msgstr "關閉"
msgid "Starting installation... The page will refresh automatically in 30 seconds." msgid "Starting installation... The page will refresh automatically in 30 seconds."
msgstr "開始安裝... 頁面將在30秒後自動重新整理。" msgstr "開始安裝... 頁面將在30秒後自動重新整理。"
@@ -1094,9 +1067,6 @@ msgstr "更新後請手動清空瀏覽器快取。"
msgid "Traffic Statistics" msgid "Traffic Statistics"
msgstr "流量統計" msgstr "流量統計"
msgid "Traffic Statistics"
msgstr "流量統計"
msgid "Device Usage Ranking" msgid "Device Usage Ranking"
msgstr "設備用量排行" msgstr "設備用量排行"
@@ -1351,3 +1321,192 @@ msgstr "無法使用"
msgid "Show reply direction" msgid "Show reply direction"
msgstr "顯示回覆方向" msgstr "顯示回覆方向"
msgid "Available space"
msgstr "可用空間"
msgid "Cannot prepare storage. Check free space and directory permissions."
msgstr "無法準備儲存空間,請檢查剩餘空間和目錄權限。"
msgid "Checking database version…"
msgstr "正在檢查資料庫版本…"
msgid "Checking storage…"
msgstr "正在檢查儲存空間…"
msgid "Choose an uncompressed .mmdb file within the upload size limit."
msgstr "請選擇未壓縮且未超過上傳大小限制的 .mmdb 檔案。"
msgid "Country/region + network organization — approximately 12 MiB"
msgstr "國家/地區 + 網路組織 · 約 12 MiB"
msgid "Country/region — approximately 3.3 MiB"
msgstr "國家/地區 · 約 3.3 MiB"
msgid "Data source"
msgstr "資料來源"
msgid "Data terms"
msgstr "資料使用條款"
msgid "Database contents"
msgstr "資料庫內容"
msgid "Database directory"
msgstr "資料庫目錄"
msgid "Database enabled"
msgstr "資料庫已啟用"
msgid "Database is up to date"
msgstr "資料庫已是最新版本"
msgid "Database operation failed"
msgstr "資料庫操作失敗"
msgid "Database operation failed. Retry or upload a local database."
msgstr "資料庫操作失敗,請重試或上傳本機資料庫。"
msgid "Database upload failed"
msgstr "資料庫上傳失敗"
msgid "Database validation failed. The previous database is unchanged."
msgstr "資料庫驗證失敗,原資料庫未變更。"
msgid "Delete"
msgstr "刪除"
msgid "Delete GeoIP databases"
msgstr "刪除 IP 歸屬地資料庫"
msgid "Disabled"
msgstr "已停用"
msgid "Documentation address"
msgstr "文件範例位址"
msgid "Download and enable"
msgstr "下載並啟用"
msgid "Download cancelled"
msgstr "下載已取消"
msgid "Downloading database…"
msgstr "正在下載資料庫…"
msgid "Enable IP location lookup"
msgstr "啟用 IP 歸屬地查詢"
msgid "Enabled"
msgstr "已啟用"
msgid "GeoIP service is unavailable. Enable connection monitoring and apply settings, or update Bandix."
msgstr "IP 歸屬地服務無法使用,請啟用連線監控並套用設定,或更新 Bandix。"
msgid "IP location database"
msgstr "IP 歸屬地資料庫"
msgid "Importing database…"
msgstr "正在匯入資料庫…"
msgid "Link-local address"
msgstr "鏈路本機位址"
msgid "Loading database…"
msgstr "正在載入資料庫…"
msgid "Local network"
msgstr "區域網路"
msgid "Loopback"
msgstr "回送位址"
msgid "Maximum upload size"
msgstr "上傳大小上限"
msgid "Multicast"
msgstr "多點傳送位址"
msgid "Not installed"
msgstr "未安裝"
msgid "Private address"
msgstr "私有位址"
msgid "Province/city (uploaded database)"
msgstr "省市(已上傳的資料庫)"
msgid "Province/city + network organization — approximately 57 MiB"
msgstr "省市 + 網路組織 · 約 57 MiB"
msgid "Remove installed databases and disable IP location lookup?"
msgstr "刪除已安裝的資料庫並停用 IP 歸屬地查詢?"
msgid "Reserved address"
msgstr "保留位址"
msgid "Shared address (CGNAT)"
msgstr "共用位址(CGNAT)"
msgid "Storage used"
msgstr "儲存用量"
msgid "The installed database could not be loaded. Download or upload it again."
msgstr "無法載入已安裝的資料庫,請重新下載或上傳。"
msgid "Unknown"
msgstr "未知"
msgid "Unknown location"
msgstr "歸屬地未知"
msgid "Unspecified address"
msgstr "未指定位址"
msgid "Update database"
msgstr "更新資料庫"
msgid "Upload an uncompressed Country, City or ASN .mmdb file. To add ASN, install a Country or City database first."
msgstr "可上傳未壓縮的 Country、City 或 ASN .mmdb 檔案。新增 ASN 前,請先安裝 Country 或 City 資料庫。"
msgid "Upload and enable"
msgstr "上傳並啟用"
msgid "Upload database"
msgstr "上傳資料庫"
msgid "Uploading database…"
msgstr "正在上傳資料庫…"
msgid "Use persistent storage or an external drive. Save and apply directory changes before downloading."
msgstr "請使用持續性儲存或外接磁碟。變更目錄後,請先儲存並套用,再下載資料庫。"
msgid "Verifying database…"
msgstr "正在驗證資料庫…"
msgid "Version"
msgstr "版本"
msgid "Country/region"
msgstr "國家/地區"
msgid "Province/city"
msgstr "省市"
msgid "Network organization"
msgstr "網路組織"
msgid "Not enough free space for this database."
msgstr "沒有足夠空間安裝此資料庫。"
msgid "Required space"
msgstr "所需空間"
msgid "Database ready"
msgstr "資料庫已就緒"
msgid "Listen Address"
msgstr "監聽位址"
msgid "IP address for Bandix to listen on. Use 127.0.0.1 for local access or 0.0.0.0 / :: for all interfaces."
msgstr "Bandix 監聽的 IP 位址。127.0.0.1 僅供本機存取,0.0.0.0 / :: 監聽所有介面。"
@@ -3,9 +3,16 @@
. /usr/share/libubox/jshn.sh . /usr/share/libubox/jshn.sh
# 从UCI配置读取端口号,如果读取失败则使用默认端口8686 # Wildcard listening addresses must be reached through loopback on the router.
BANDIX_PORT=$(uci get bandix.general.port 2>/dev/null || echo "8686") BANDIX_HOST=$(uci -q get bandix.general.host)
readonly BANDIX_API_BASE="http://127.0.0.1:$BANDIX_PORT" BANDIX_PORT=$(uci -q get bandix.general.port)
BANDIX_PORT=${BANDIX_PORT:-8686}
case "$BANDIX_HOST" in
''|0.0.0.0) BANDIX_HOST='127.0.0.1' ;;
::) BANDIX_HOST='[::1]' ;;
*:*) BANDIX_HOST="[$BANDIX_HOST]" ;;
esac
readonly BANDIX_API_BASE="http://$BANDIX_HOST:$BANDIX_PORT"
readonly BANDIX_DEVICES_API="$BANDIX_API_BASE/api/traffic/devices" readonly BANDIX_DEVICES_API="$BANDIX_API_BASE/api/traffic/devices"
readonly BANDIX_LIMITS_API="$BANDIX_API_BASE/api/traffic/limits" readonly BANDIX_LIMITS_API="$BANDIX_API_BASE/api/traffic/limits"
readonly BANDIX_METRICS_API="$BANDIX_API_BASE/api/traffic/metrics" readonly BANDIX_METRICS_API="$BANDIX_API_BASE/api/traffic/metrics"
@@ -421,12 +428,13 @@ get_connection_flows() {
[ -n "$state" ] && query_params="${query_params}state=$(printf '%s' "$state" | sed 's/ /%20/g')&" [ -n "$state" ] && query_params="${query_params}state=$(printf '%s' "$state" | sed 's/ /%20/g')&"
[ -n "$page" ] && query_params="${query_params}page=$page&" [ -n "$page" ] && query_params="${query_params}page=$page&"
[ -n "$page_size" ] && query_params="${query_params}page_size=$page_size&" [ -n "$page_size" ] && query_params="${query_params}page_size=$page_size&"
if [ -n "$page" ] || [ -n "$page_size" ]; then query_params="${query_params}geoip=1&"; fi
query_params=$(echo "$query_params" | sed 's/&$//') query_params=$(echo "$query_params" | sed 's/&$//')
local url="$BANDIX_CONNECTION_FLOWS_API" local url="$BANDIX_CONNECTION_FLOWS_API"
[ -n "$query_params" ] && url="${url}?${query_params}" [ -n "$query_params" ] && url="${url}?${query_params}"
local response=$(curl -s --connect-timeout 2 --max-time 5 -X GET "$url" 2>/dev/null) local response=$(curl -s --connect-timeout 2 --max-time 15 -X GET "$url" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$response" ]; then if [ $? -ne 0 ] || [ -z "$response" ]; then
make_error "Failed to connect to Bandix service" make_error "Failed to connect to Bandix service"
return return
@@ -434,6 +442,44 @@ get_connection_flows() {
echo "$response" echo "$response"
} }
# GeoIP management is proxied to the router itself; writes require the LuCI
# write ACL. Accept only action/profile identifiers, never arbitrary URLs/paths.
get_geoip_status() {
local response
response=$(curl -s --connect-timeout 2 --max-time 8 "$BANDIX_API_BASE/api/geoip/status" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$response" ]; then
make_error "GeoIP service is unavailable"
return
fi
printf '%s\n' "$response"
}
manage_geoip() {
local action="$1" profile="$2" enabled="$3" payload response
json_init
case "$action" in
download)
case "$profile" in country|country_asn|city|city_asn) ;; *) make_error "Invalid database profile"; return ;; esac
json_add_string "action" "download"
json_add_string "profile" "$profile"
;;
enable)
json_add_string "action" "enable"
case "$enabled" in 1|true) json_add_boolean "enabled" 1 ;; 0|false) json_add_boolean "enabled" 0 ;; *) make_error "Invalid enabled value"; return ;; esac
;;
cancel|delete|import) json_add_string "action" "$action" ;;
*) make_error "Invalid GeoIP action"; return ;;
esac
payload=$(json_dump)
json_cleanup
response=$(curl -s --connect-timeout 2 --max-time 8 -X POST -H 'Content-Type: application/json' --data-binary "$payload" "$BANDIX_API_BASE/api/geoip/manage" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$response" ]; then
make_error "GeoIP service is unavailable"
return
fi
printf '%s\n' "$response"
}
# 获取 DNS 查询记录 # 获取 DNS 查询记录
get_dns_queries() { get_dns_queries() {
# 检查 DNS 监控是否启用 # 检查 DNS 监控是否启用
@@ -1311,6 +1357,14 @@ case "$1" in
json_add_string "mac" json_add_string "mac"
json_close_object json_close_object
json_add_object "getGeoipStatus"
json_close_object
json_add_object "manageGeoip"
json_add_string "action"
json_add_string "profile"
json_add_string "enabled"
json_close_object
json_add_object "getConnection" json_add_object "getConnection"
json_close_object json_close_object
@@ -1525,6 +1579,20 @@ case "$1" in
fi fi
delete_device "$mac" delete_device "$mac"
;; ;;
getGeoipStatus)
get_geoip_status
;;
manageGeoip)
input=""
read -r input
action="$(printf '%s' "$input" | jsonfilter -e '$.action' 2>/dev/null)"
[ -z "$action" ] && action="$(printf '%s' "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
profile="$(printf '%s' "$input" | jsonfilter -e '$.profile' 2>/dev/null)"
[ -z "$profile" ] && profile="$(printf '%s' "$input" | jsonfilter -e '$[1]' 2>/dev/null)"
enabled="$(printf '%s' "$input" | jsonfilter -e '$.enabled' 2>/dev/null)"
[ -z "$enabled" ] && enabled="$(printf '%s' "$input" | jsonfilter -e '$[2]' 2>/dev/null)"
manage_geoip "$action" "$profile" "$enabled"
;;
getConnection) getConnection)
# logger "luci.bandix: getConnection called" # logger "luci.bandix: getConnection called"
get_connection_devices get_connection_devices
@@ -27,7 +27,8 @@
"getTrafficUsageRanking", "getTrafficUsageRanking",
"getTrafficUsageIncrements", "getTrafficUsageIncrements",
"getRateLimitWhitelist", "getRateLimitWhitelist",
"getTrafficQuotas" "getTrafficQuotas",
"getGeoipStatus"
] ]
}, },
"uci": [ "uci": [
@@ -68,12 +69,19 @@
"updateScheduleLimit", "updateScheduleLimit",
"getTrafficQuotas", "getTrafficQuotas",
"setTrafficQuota", "setTrafficQuota",
"deleteTrafficQuota" "deleteTrafficQuota",
"getGeoipStatus",
"manageGeoip"
] ]
}, },
"uci": [ "uci": [
"bandix" "bandix"
] ],
"file": {
"/tmp/bandix-geoip-upload.mmdb": [
"write"
]
}
} }
} }
} }
+2 -2
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-clashoo PKG_NAME:=luci-app-clashoo
PKG_VERSION:=1.31.0 PKG_VERSION:=1.31.0
PKG_RELEASE:=8 PKG_RELEASE:=9
PKG_MAINTAINER:=kenzok8 PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@@ -91,7 +91,7 @@ define Package/luci-i18n-clashoo-zh-cn/postinst
if [ -z "$${IPKG_INSTROOT}" ]; then if [ -z "$${IPKG_INSTROOT}" ]; then
# just clear the LuCI cache so the new translations load on next request; # just clear the LuCI cache so the new translations load on next request;
# no service restart needed (and none that would drop the login session). # no service restart needed (and none that would drop the login session).
rm -rf /tmp/luci* rm -rf /tmp/luci-indexcache /tmp/luci-modulecache
fi fi
exit 0 exit 0
endef endef
@@ -15,7 +15,9 @@ function mkdtemp(tpl) {
} }
const BINARY_PATHS = [ const BINARY_PATHS = [
'/usr/bin/mihomo-stable', '/usr/share/clashoo/bin/mihomo-stable',
'/usr/libexec/clashoo/mihomo-stable',
'/usr/share/clashoo/bin/mihomo-alpha',
'/usr/bin/mihomo', '/usr/bin/mihomo',
'/usr/bin/clash-meta', '/usr/bin/clash-meta',
'/usr/bin/clash', '/usr/bin/clash',
@@ -283,8 +285,8 @@ if (family === 'mihomo') {
} }
} }
let channel = get_core_channel('mihomo'); let channel = get_core_channel('mihomo');
if (channel == 'stable' && access('/usr/bin/mihomo-stable', 'x')) return '/usr/bin/mihomo-stable'; if (channel == 'stable') return selected_mihomo_stable();
if (channel == 'alpha' && access('/usr/bin/mihomo', 'x')) return '/usr/bin/mihomo'; if (channel == 'alpha') return access('/usr/share/clashoo/bin/mihomo-alpha', 'x') ? '/usr/share/clashoo/bin/mihomo-alpha' : '';
if (channel == 'smart') { if (channel == 'smart') {
for (let path in SMART_BINARY_PATHS) for (let path in SMART_BINARY_PATHS)
if (access(path, 'x')) return path; if (access(path, 'x')) return path;
@@ -371,7 +373,7 @@ c.set('clashoo', 'config', 'core', legacy);
} }
function has_mihomo_stable() { function has_mihomo_stable() {
if (access('/usr/bin/mihomo-stable', 'x')) return true; if (selected_mihomo_stable()) return true;
if (system("[ -x /usr/bin/clash-meta ] && [ ! -L /usr/bin/clash-meta ]") != 0) return false; if (system("[ -x /usr/bin/clash-meta ] && [ ! -L /usr/bin/clash-meta ]") != 0) return false;
let version = get_core_version('/usr/bin/clash-meta'); let version = get_core_version('/usr/bin/clash-meta');
return !!version && !match(version, /(alpha|beta|rc|pre|smart)/i); return !!version && !match(version, /(alpha|beta|rc|pre|smart)/i);
@@ -387,7 +389,7 @@ if (core == 'mihomo' && dcore == '2') {
return { error: 'mihomo_stable_missing', message: '未检测到 mihomo 稳定版,请先在“组件更新”中更新稳定版' }; return { error: 'mihomo_stable_missing', message: '未检测到 mihomo 稳定版,请先在“组件更新”中更新稳定版' };
} }
if (core == 'mihomo' && dcore == '3') { if (core == 'mihomo' && dcore == '3') {
if (access('/usr/bin/mihomo', 'x')) return null; if (access('/usr/share/clashoo/bin/mihomo-alpha', 'x')) return null;
return { error: 'mihomo_alpha_missing', message: '未检测到 mihomo Alpha,请先在“组件更新”中更新 Alpha' }; return { error: 'mihomo_alpha_missing', message: '未检测到 mihomo Alpha,请先在“组件更新”中更新 Alpha' };
} }
if (core == 'singbox' && dcore == '4') { if (core == 'singbox' && dcore == '4') {
@@ -887,6 +889,23 @@ if (m) return m[1];
return full; return full;
} }
function selected_mihomo_stable() {
let bundled = '/usr/libexec/clashoo/mihomo-stable';
let manual = '/usr/share/clashoo/bin/mihomo-stable';
if (access(manual, 'x') && read_first_line('/usr/share/clashoo/clash_meta_version') == 'custom-url') return manual;
if (!access(bundled, 'x')) return access(manual, 'x') ? manual : '';
if (!access(manual, 'x')) return bundled;
let bv = match(core_short_version(bundled), /^v?([0-9]+)\.([0-9]+)\.([0-9]+)$/);
let mv = match(core_short_version(manual), /^v?([0-9]+)\.([0-9]+)\.([0-9]+)$/);
if (!bv) return mv ? manual : bundled;
if (!mv) return bundled;
for (let i = 1; i <= 3; i++) {
if (int(mv[i]) > int(bv[i])) return manual;
if (int(mv[i]) < int(bv[i])) return bundled;
}
return manual;
}
function component_installed_label(id) { function component_installed_label(id) {
if (id == 'clashoo') return pkg_version('clashoo') || '未安装'; if (id == 'clashoo') return pkg_version('clashoo') || '未安装';
if (id == 'luci') return pkg_version('luci-app-clashoo') || '未安装'; if (id == 'luci') return pkg_version('luci-app-clashoo') || '未安装';
@@ -923,12 +942,11 @@ return !!match(ver || '', /(alpha|beta|rc|pre|smart)/i);
function component_installed_versions(id) { function component_installed_versions(id) {
let out = {}; let out = {};
if (id == 'mihomo') { if (id == 'mihomo') {
let stable = access('/usr/bin/mihomo-stable', 'x') ? read_first_line('/usr/share/clashoo/clash_meta_version') : ''; let stable_bin = selected_mihomo_stable();
if (!stable && access('/usr/bin/mihomo-stable', 'x')) stable = core_short_version('/usr/bin/mihomo-stable'); let stable = stable_bin ? core_short_version(stable_bin) : '';
if (is_prerelease_label(stable)) stable = ''; if (is_prerelease_label(stable)) stable = '';
let alpha = read_first_line('/usr/share/clashoo/mihomo_version'); let alpha = access('/usr/share/clashoo/bin/mihomo-alpha', 'x') ? core_short_version('/usr/share/clashoo/bin/mihomo-alpha') : '';
if (match(alpha, /^smart-/)) alpha = ''; if (match(alpha, /^smart-/)) alpha = '';
if (!alpha && access('/usr/bin/mihomo', 'x')) alpha = core_short_version('/usr/bin/mihomo');
out.stable = stable || ''; out.stable = stable || '';
out.alpha = mihomo_alpha_label(alpha); out.alpha = mihomo_alpha_label(alpha);
return out; return out;
@@ -2815,7 +2833,7 @@ let api = get_clash_api_auth();
health_status, health_detail, has_tun_device, health_status, health_detail, has_tun_device,
core_type: configured_core, dcore, local_ip, core_type: configured_core, dcore, local_ip,
has_mihomo_stable: has_mihomo_stable(), has_mihomo_stable: has_mihomo_stable(),
has_mihomo_alpha: !!access('/usr/bin/mihomo', 'x'), has_mihomo_alpha: !!access('/usr/share/clashoo/bin/mihomo-alpha', 'x'),
has_singbox_stable: !!access(SINGBOX_BINARY_PATHS[0], 'x'), has_singbox_stable: !!access(SINGBOX_BINARY_PATHS[0], 'x'),
has_singbox_alpha: !!access(SINGBOX_BINARY_PATHS[1], 'x'), has_singbox_alpha: !!access(SINGBOX_BINARY_PATHS[1], 'x'),
has_smart: !!find_smart_binary() }; has_smart: !!find_smart_binary() };
+2 -2
View File
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for fastnet LUCI_TITLE:=LuCI support for fastnet
LUCI_DEPENDS:=+fastnet +luci-lib-linkeaseauth +linkease-app-entry LUCI_DEPENDS:=+fastnet +luci-lib-linkeaseauth +linkease-app-entry
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
PKG_VERSION:=0.7.7-r5 PKG_VERSION:=0.7.8-r5
PKG_RELEASE:=4 PKG_RELEASE:=5
LUCI_MINIFY_CSS:=0 LUCI_MINIFY_CSS:=0
LUCI_MINIFY_JS:=0 LUCI_MINIFY_JS:=0
+2 -2
View File
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for KSpeeder LUCI_TITLE:=LuCI support for KSpeeder
LUCI_DEPENDS:=+istoreenhance +luci-lib-linkeaseauth +linkease-app-entry LUCI_DEPENDS:=+istoreenhance +luci-lib-linkeaseauth +linkease-app-entry
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
PKG_VERSION:=0.8.4-r1 PKG_VERSION:=0.8.5-r1
PKG_RELEASE:=7 PKG_RELEASE:=8
include $(TOPDIR)/feeds/luci/luci.mk include $(TOPDIR)/feeds/luci/luci.mk
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-open-box PKG_NAME:=luci-app-open-box
PKG_VERSION:=0.1.361 PKG_VERSION:=0.1.366
PKG_RELEASE:=47 PKG_RELEASE:=48
PKG_MAINTAINER:=liandu2024 PKG_MAINTAINER:=liandu2024
PKG_LICENSE:=GPL-3.0-or-later PKG_LICENSE:=GPL-3.0-or-later
+1 -1
View File
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-openvpn-plus PKG_NAME:=luci-app-openvpn-plus
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=5 PKG_RELEASE:=6
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Manfred Jaider <info@masmbit.com> PKG_MAINTAINER:=Manfred Jaider <info@masmbit.com>
-25
View File
@@ -60,31 +60,6 @@ config openvpn 'instance1'
option config '/etc/openvpn/luci/instance1.conf' option config '/etc/openvpn/luci/instance1.conf'
``` ```
## Screenshots
<table>
<tr>
<td><img width="2442" height="1844" alt="01" src="https://github.com/user-attachments/assets/5f215704-c18e-42ba-9228-c100635d3836" /></td>
<td><img width="2398" height="1700" alt="03" src="https://github.com/user-attachments/assets/10ee2c26-6ca8-42c5-9f1e-04f73fb9c91c" /></td>
<td><img width="2384" height="1706" alt="04" src="https://github.com/user-attachments/assets/86f7c341-37e2-4be9-8b7a-458cff1cc722" /></td>
</tr>
<tr>
<td><img width="1796" height="556" alt="09" src="https://github.com/user-attachments/assets/c2d2b23d-b3b8-40f3-a1bf-b9d57f1ca4e3" /></td>
<td><img width="1798" height="496" alt="10" src="https://github.com/user-attachments/assets/30ff6070-def5-4dd8-a5a9-4de11fa14706" /></td>
<td><img width="1798" height="754" alt="13" src="https://github.com/user-attachments/assets/9d4d897c-18b1-44dc-93de-035984acf5db" /></td>
</tr>
<tr>
<td><img width="1800" height="1352" alt="11" src="https://github.com/user-attachments/assets/ae0c5c01-10f3-496a-b687-7b6a734ad322" /></td>
<td><img width="1798" height="1728" alt="12" src="https://github.com/user-attachments/assets/f5023929-99b5-438a-94e2-6042c3d352a9" /></td>
<td><img width="2412" height="1692" alt="08" src="https://github.com/user-attachments/assets/937b8f11-4743-4aca-bf63-8b5003ce7cda" /></td>
</tr>
</td>
<td><img width="2406" height="1834" alt="14" src="https://github.com/user-attachments/assets/f9d64535-4177-4b86-bd6a-f1611dc41da8" /></td>
<td><img width="2394" height="1808" alt="06" src="https://github.com/user-attachments/assets/008c6f5b-0636-43e2-afe6-1de835eab4af" /></td>
<td><img width="2382" height="1792" alt="05" src="https://github.com/user-attachments/assets/5694833e-2b03-43a2-b1ff-8f3afe11c8e0" /></td>
</tr>
</table>
## Directory Structure ## Directory Structure
```text ```text
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -30,7 +30,7 @@ tls-groups secp256r1
cipher AES-256-GCM cipher AES-256-GCM
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305 data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
# --- OpenVPN Flood Guard --- # --- Flood Guard Filter ---
setenv floodguard on setenv floodguard on
# --- Client Push Options --- # --- Client Push Options ---
@@ -471,16 +471,18 @@ openssldgst() {
# Manage temporary download links for mobile profiles [create|delete] # Manage temporary download links for mobile profiles [create|delete]
symlink() { symlink() {
shift # Remove "symlink" from the arguments list shift # Remove "symlink" from the arguments list
local ovpn_num="$1" local ovpn_instance="$1"
local action="$2" local action="$2"
local raw_display_name="$3" local raw_display_name="$3"
local export_static_path="/etc/openvpn/luci/instance${ovpn_num}.ovpn" local export_static_path="/etc/openvpn/luci/${ovpn_instance}.ovpn"
local lan_ip="" local lan_ip=""
local link_filename="" local link_filename=""
local ram_link_dir="/tmp/luci-ovpn-plus"
local www_link_dir="/www/ovpn-downloads"
# Check if instance number is a valid digit. # Check if instance name is provided and safe (alphanumeric only).
if [ -z "$ovpn_num" ] || ! echo "$ovpn_num" | grep -q '^[0-9]\+$'; then if [ -z "$ovpn_instance" ] || ! echo "$ovpn_instance" | grep -q '^[A-Za-z0-9]\+$'; then
echo "ERROR: Invalid instance number." echo "ERROR: Invalid instance name."
return 1 return 1
fi fi
@@ -491,53 +493,66 @@ symlink() {
# Fallback if the cleaned name is empty. # Fallback if the cleaned name is empty.
if [ -z "$link_filename" ]; then if [ -z "$link_filename" ]; then
link_filename="instance${ovpn_num}" link_filename="${ovpn_instance}"
fi fi
case "$action" in case "$action" in
create) create)
# Check if the source profile exists on disk.
if [ ! -f "$export_static_path" ]; then if [ ! -f "$export_static_path" ]; then
echo "ERROR: Profile file missing at $export_static_path" echo "ERROR: Profile file missing at $export_static_path"
return 1 return 1
fi fi
# Create a random crypt-token (9 random bytes as hex). # Create a 9-byte random secret token as hex
local secret_token local secret_token
secret_token=$(dd if=/dev/urandom bs=9 count=1 2>/dev/null | hexdump -e '"%02x"') secret_token=$(dd if=/dev/urandom bs=9 count=1 2>/dev/null | hexdump -e '"%02x"')
# Path to the temporary session folder. local session_dir="${ram_link_dir}/ovpn-tmp-${secret_token}-${link_filename}"
local session_dir="/www/ovpn-tmp-${secret_token}-${link_filename}"
local token_protected_file="${session_dir}/${link_filename}_client.ovpn"
# Create the directory safely.
mkdir -p "$session_dir" mkdir -p "$session_dir"
# SECURITY FIX: create token-protected copy for the webserver # Define file path and copy profile with safe webserver permissions
local token_protected_file="${session_dir}/${link_filename}.ovpn"
cp -f "$export_static_path" "$token_protected_file" 2>/dev/null cp -f "$export_static_path" "$token_protected_file" 2>/dev/null
chmod 644 "$token_protected_file" chmod 644 "$token_protected_file"
# Verify that the file copy exists and is valid. # Create webserver shortcut to the RAM folder
if [ ! -L "$www_link_dir" ] && [ ! -d "$www_link_dir" ]; then
ln -s "$ram_link_dir" "$www_link_dir"
fi
if [ -f "$token_protected_file" ]; then if [ -f "$token_protected_file" ]; then
lan_ip=$(lanip) lan_ip=$(lanip)
if [ -z "$lan_ip" ] || [ "$lan_ip" = "0.0.0.0" ]; then if [ -z "$lan_ip" ] || [ "$lan_ip" = "0.0.0.0" ]; then
lan_ip="localhost" lan_ip="localhost"
# If the IP contains a colon (:), it is an IPv6 address and needs brackets []
elif echo "$lan_ip" | grep -q ':'; then
lan_ip="[${lan_ip}]"
fi fi
# Return SUCCESS and the clean download link with sub-folder echo "SUCCESS: http://${lan_ip}/ovpn-downloads/ovpn-tmp-${secret_token}-${link_filename}/${link_filename}.ovpn"
echo "SUCCESS: http://${lan_ip}/ovpn-tmp-${secret_token}-${link_filename}/${link_filename}_client.ovpn"
else else
echo "ERROR: Failed to create web export file." echo "ERROR: Failed to create web export file."
return 1 return 1
fi fi
;; ;;
delete) delete)
# Safe clean up using wildcard on the directory name. # Safe deletion inside RAM directory only
# This removes the folder, the temporary copy, and deletes the static file. if [ -n "$link_filename" ] && [ -d "$ram_link_dir" ]; then
rm -rf /www/ovpn-tmp-*-"${link_filename}" rm -rf "${ram_link_dir}"/ovpn-tmp-*-"${link_filename}"
echo "SUCCESS: Link directory and static profile deleted."
# Remove RAM base directory and the web symlink if it was the last file
rmdir "$ram_link_dir" 2>/dev/null
if [ ! -d "$ram_link_dir" ] && [ -L "$www_link_dir" ]; then
rm -f "$www_link_dir"
fi
echo "SUCCESS: Link directory and static profile deleted from RAM."
else
echo "ERROR: Invalid deletion request."
return 1
fi
;; ;;
*) *)
echo "Usage: /usr/libexec/luci-app-openvpn-plus symlink [num] [create|delete] [display_name]" echo "Usage: /usr/libexec/luci-app-openvpn-plus symlink [instance_id] [create|delete] [display_name]"
return 1 return 1
;; ;;
esac esac
@@ -1357,6 +1372,8 @@ cleanup() {
local instname local instname
local conf_dir="/etc/openvpn/luci" local conf_dir="/etc/openvpn/luci"
local key_dir="/etc/openvpn/keys" local key_dir="/etc/openvpn/keys"
local ram_link_dir="/tmp/luci-ovpn-plus"
local www_link_dir="/www/ovpn-downloads"
# Step 1: Stop here if there are no instance config files on the router # Step 1: Stop here if there are no instance config files on the router
ls /etc/openvpn/luci/instance*.conf >/dev/null 2>&1 || return 0 ls /etc/openvpn/luci/instance*.conf >/dev/null 2>&1 || return 0
@@ -1364,7 +1381,6 @@ cleanup() {
# Step 2: Loop through all existing instance configuration files # Step 2: Loop through all existing instance configuration files
for conf_file in "$conf_dir"/instance*.conf; do for conf_file in "$conf_dir"/instance*.conf; do
if [ -f "$conf_file" ]; then if [ -f "$conf_file" ]; then
# Extract the pure instance name from the file path
instname="${conf_file##*/}" instname="${conf_file##*/}"
instname="${instname%.conf}" instname="${instname%.conf}"
@@ -1382,17 +1398,31 @@ cleanup() {
rm -f "$key_dir/client_${instname}.key" rm -f "$key_dir/client_${instname}.key"
rm -f "$key_dir/server_${instname}.tls2.key" rm -f "$key_dir/server_${instname}.tls2.key"
rm -f "$key_dir/client_${instname}.tls2.key" rm -f "$key_dir/client_${instname}.tls2.key"
# Delete the static .ovpn file for this specific deleted instance
rm -f "$conf_dir/${instname}.ovpn"
# Clean up the specific RAM files for this deleted instance
if [ -d "$ram_link_dir" ]; then
rm -rf "${ram_link_dir}"/ovpn-tmp-*-*"${instname}"
fi
fi fi
fi fi
done done
# Step 4: Delete all remaining temporary symlink files from the system # Step 4: Safely clean up the empty base directories and the web symlink
rm -rf /www/ovpn-tmp-* if [ -d "$ram_link_dir" ]; then
rm -f "$conf_dir"/instance*.ovpn # Removes the RAM base folder ONLY if it is completely empty
rmdir "$ram_link_dir" 2>/dev/null
fi
# Clean up the web shortcut if the RAM directory does not exist anymore
if [ ! -d "$ram_link_dir" ] && [ -L "$www_link_dir" ]; then
rm -f "$www_link_dir"
fi
return 0 return 0
} }
# Validate and extract cryptographic metadata for LuCI # Validate and extract cryptographic metadata for LuCI
keymeta() { keymeta() {
shift # Remove "keymeta" from the arguments list shift # Remove "keymeta" from the arguments list
@@ -1475,7 +1505,7 @@ keymeta() {
fi fi
fi fi
else else
echo "TYPE: Custom Raw Data Structure" echo "INVALID: Custom Raw Data Structure"
fi fi
else else
echo "ERROR: File is empty or missing." echo "ERROR: File is empty or missing."
@@ -33,6 +33,7 @@ const TXT = {
client: _('Client') client: _('Client')
}, },
BTN: { BTN: {
backup: _('Backup'),
close: _('Close'), close: _('Close'),
cancel: _('Cancel'), cancel: _('Cancel'),
download: _('Download'), download: _('Download'),
@@ -57,6 +58,7 @@ const TXT = {
generate_unique_keys: _('Yes (Generate Unique Keys)'), generate_unique_keys: _('Yes (Generate Unique Keys)'),
generate_unique_keys_new_instance: _('To be secure, a new instance needs its own key files. Do you want to create them now? (Recommended - usually takes just a few moments)'), generate_unique_keys_new_instance: _('To be secure, a new instance needs its own key files. Do you want to create them now? (Recommended - usually takes just a few moments)'),
generating_client_keys: _('Generating private client key...'), generating_client_keys: _('Generating private client key...'),
generating_tls_client_keys: _('Generating TLS crypt v2 client key...'),
key_strength: _('Key Strength'), key_strength: _('Key Strength'),
loading_contents: _('Loading key contents...'), loading_contents: _('Loading key contents...'),
master_ca: _('Master CA'), master_ca: _('Master CA'),
@@ -89,8 +91,9 @@ const TXT = {
tls_key_server: _('Master Server Secret Key'), tls_key_server: _('Master Server Secret Key'),
tls_key_client: _('Client Key'), tls_key_client: _('Client Key'),
title_main: _('OpenVPN Instance Key Generator'), title_main: _('OpenVPN Instance Key Generator'),
key_allocation: ('Key Allocation'), add_server_instance: ('Add OpenVPN Server Instance'),
unknown_asset: _('Unknown Asset'), unknown_asset: _('Unknown Asset'),
unknown_structure: _('Unknown Structure'),
use_unique_name_for_each_office: _('Important: Use a unique name for each office to configure the network routing correctly.'), use_unique_name_for_each_office: _('Important: Use a unique name for each office to configure the network routing correctly.'),
validity_days: _('Validity (Days)'), validity_days: _('Validity (Days)'),
vpn_key_type: _('VPN Key Type:') vpn_key_type: _('VPN Key Type:')
@@ -105,6 +108,7 @@ const TXT = {
ERROR: { ERROR: {
format_corruption: _('The saved key data contains structural format corruption!'), format_corruption: _('The saved key data contains structural format corruption!'),
polling_threshold: _('ERROR: Safety polling threshold exceeded boundaries. Process deadlocked.'), polling_threshold: _('ERROR: Safety polling threshold exceeded boundaries. Process deadlocked.'),
tls_keygen_failed: _('TLS crypt v2 client key generation failed.')
} }
} }
@@ -210,7 +214,7 @@ const getKeyMeta = async function (keyfile, luci_app_openvpn_plus_callback) {
const result = { const result = {
valid: false, valid: false,
type: CRYPTO_TYPE.INVALID_KEY, type: CRYPTO_TYPE.INVALID_KEY,
metadata: { type: _('Unknown Structure'), subject: '', expires: '', algo: '', curve: '' }, metadata: { type: TXT.KEY.unknown_structure, subject: '', expires: '', algo: '', curve: '' },
raw: '' raw: ''
}; };
@@ -221,10 +225,14 @@ const getKeyMeta = async function (keyfile, luci_app_openvpn_plus_callback) {
} }
// 2. Safely trigger the verified callback mechanism // 2. Safely trigger the verified callback mechanism
const res = await luci_app_openvpn_plus_callback([CFG.LIBEXEC.keymeta, keyfile]); let res = await luci_app_openvpn_plus_callback([CFG.LIBEXEC.keymeta, keyfile]);
// Early escape if the backend fails or outputs errors // Early escape if the backend fails or outputs errors
if (!res || res.length === 0 || res.startsWith('ERROR') || res.toUpperCase().indexOf('ERROR') !== -1) { if (!res || res.length === 0) {
return result;
}
res = res.toUpperCase();
if (res.startsWith('ERROR') || res.indexOf('ERROR') !== -1 || res.startsWith('INVALID')) {
return result; return result;
} }
@@ -769,10 +777,11 @@ const openAutomatedPostKeyGenModal = async function (newInstanceItem, appData, w
const yesBtn = E('button', { 'class': 'btn cbi-button cbi-button-action important', 'style': 'margin-right:10px;' }, TXT.KEY.generate_unique_keys); const yesBtn = E('button', { 'class': 'btn cbi-button cbi-button-action important', 'style': 'margin-right:10px;' }, TXT.KEY.generate_unique_keys);
const noBtn = E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'style': 'margin-right:10px;' }, TXT.KEY.clone_default_keys); const noBtn = E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'style': 'margin-right:10px;' }, TXT.KEY.clone_default_keys);
const backupBtn = E('button', { 'class': 'btn cbi-button cbi-button-positive important', 'style': 'margin-right:10px;' }, TXT.BTN.backup);
const cancelBtn = E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'style': 'margin-right:10px;' + styleHide }, TXT.BTN.cancel); const cancelBtn = E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'style': 'margin-right:10px;' + styleHide }, TXT.BTN.cancel);
const closeBtn = E('button', { 'class': 'cbi-button cbi-button-action important', 'style': 'display:none;' }, TXT.BTN.close); const closeBtn = E('button', { 'class': 'cbi-button cbi-button-action important', 'style': 'display:none;' }, TXT.BTN.close);
const buttonContainer = E('div', { 'style': 'text-align:right; margin-top:15px;' }, [yesBtn, noBtn, cancelBtn, closeBtn]); const buttonContainer = E('div', { 'style': 'text-align:right; margin-top:15px;' }, [yesBtn, noBtn, backupBtn, cancelBtn, closeBtn]);
const dashedDivider = E('hr', { const dashedDivider = E('hr', {
'style': 'margin:20px 0; border:0; border-top:1px dashed var(--border-color-medium, #cbd5e1);' + styleHide 'style': 'margin:20px 0; border:0; border-top:1px dashed var(--border-color-medium, #cbd5e1);' + styleHide
@@ -803,7 +812,6 @@ const openAutomatedPostKeyGenModal = async function (newInstanceItem, appData, w
E('span', {}, TXT.INFO.wizard + ' ...') E('span', {}, TXT.INFO.wizard + ' ...')
]); ]);
const wizardButtonWrapperRow = E('div', { const wizardButtonWrapperRow = E('div', {
'style': 'display:flex; justify-content:flex-end; width:100%; margin-top:5px;' + styleHide 'style': 'display:flex; justify-content:flex-end; width:100%; margin-top:5px;' + styleHide
}, [openWizardBtn]); }, [openWizardBtn]);
@@ -820,6 +828,12 @@ const openAutomatedPostKeyGenModal = async function (newInstanceItem, appData, w
callbacks.openWizardBtnClick(appData, null, true); callbacks.openWizardBtnClick(appData, null, true);
}); });
backupBtn.addEventListener('click', async function (ev) {
ev.preventDefault();
L.ui.hideModal();
callbacks.importBackupInstance(appData);
});
// Declared anonymous handler as ASYNC to authorize internal await callbacks.createInstance // Declared anonymous handler as ASYNC to authorize internal await callbacks.createInstance
noBtn.addEventListener('click', async function (ev) { noBtn.addEventListener('click', async function (ev) {
ev.preventDefault(); ev.preventDefault();
@@ -889,7 +903,7 @@ const openAutomatedPostKeyGenModal = async function (newInstanceItem, appData, w
callbacks.uciSaveApplyRestart(instance_id); callbacks.uciSaveApplyRestart(instance_id);
}); });
L.ui.showModal(TXT.KEY.title_main + ' - ' + TXT.KEY.key_allocation + ' (' + instance_id + ')', [ L.ui.showModal(TXT.KEY.add_server_instance + ' (' + instance_id + ')', [
E('div', { 'class': 'cbi-map' }, [ E('div', { 'class': 'cbi-map' }, [
E('div', { 'class': 'cbi-section' }, [ E('div', { 'class': 'cbi-section' }, [
promptMessageNode, promptMessageNode,
@@ -1148,7 +1162,7 @@ const setupKeyGenEvents = function (ui, instance_id, state, appData, uciSaveAppl
// Scenario A: If a Site-to-Site client bundle is requested, chain the asymmetric client token derivation instantly // Scenario A: If a Site-to-Site client bundle is requested, chain the asymmetric client token derivation instantly
if (state.mode === KEYTYPE.client_pki) { if (state.mode === KEYTYPE.client_pki) {
const log_separator_line = '\n\n--------------------------------------------------\n\n'; const log_separator_line = '\n\n--------------------------------------------------\n\n';
ui.nodes.output.value += log_separator_line + ICON.ARROW + _('Generating TLS crypt v2 client key...') + '\n'; ui.nodes.output.value += log_separator_line + ICON.ARROW + TXT.KEY.generating_tls_client_keys + '\n';
ui.nodes.output.scrollTop = ui.nodes.output.scrollHeight; ui.nodes.output.scrollTop = ui.nodes.output.scrollHeight;
// STEP 2: Asynchronously generate the client-tls2 credentials into the volatile RAM buffer // STEP 2: Asynchronously generate the client-tls2 credentials into the volatile RAM buffer
@@ -1157,7 +1171,7 @@ const setupKeyGenEvents = function (ui, instance_id, state, appData, uciSaveAppl
modeSelect.disabled = false; modeSelect.disabled = false;
if (!tlsSuccess || !tlsData) { if (!tlsSuccess || !tlsData) {
ui.nodes.output.value += '\n' + ICON.ERROR + ' TLS crypt v2 client key generation failed.'; ui.nodes.output.value += '\n' + ICON.ERROR + TXT.ERROR.tls_keygen_failed;
return; return;
} }
@@ -1338,14 +1352,13 @@ const openKeyGenModal = function (instance_id, displayId, role, appData, uciSave
/** /**
* Export the open wizard modal function to LuCI * Export the open wizard modal function to LuCI
*/ */
return L.Class.extend( return L.Class.extend({
{ KEYTYPE: KEYTYPE,
KEYTYPE: KEYTYPE, CRYPTO_TYPE: CRYPTO_TYPE,
CRYPTO_TYPE: CRYPTO_TYPE, getKeyMeta: getKeyMeta,
getKeyMeta: getKeyMeta, executeAsynchronousKeyGen: executeAsynchronousKeyGen,
executeAsynchronousKeyGen: executeAsynchronousKeyGen, openKeyEditorModal: openKeyEditorModal,
openKeyEditorModal: openKeyEditorModal, openAutomatedPostKeyGenModal: openAutomatedPostKeyGenModal,
openAutomatedPostKeyGenModal: openAutomatedPostKeyGenModal, openKeyGenModal: openKeyGenModal
openKeyGenModal: openKeyGenModal });
});
@@ -142,6 +142,7 @@ const CLIENT_TEMPLATE = Object.freeze({
timeMs: 0, timeMs: 0,
}); });
/* /*
* --- HELPER --- * --- HELPER ---
*/ */
@@ -153,6 +154,9 @@ const CLIENT_TEMPLATE = Object.freeze({
const configAssetCache = {}; const configAssetCache = {};
const statusFileCache = {}; const statusFileCache = {};
/**
* Normalizes all Windows and Mac line breaks into clean UNIX line breaks for textareas.
*/
const sanitizeInputText = function (value) { const sanitizeInputText = function (value) {
const rawText = value ? String(value).trim() : ''; const rawText = value ? String(value).trim() : '';
return rawText.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); return rawText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
@@ -270,6 +274,9 @@ const isAnyInstanceEnabled = function (ovpnUciSections) {
return false; return false;
}; };
/**
* Caches openVpnInfo
*/
const openVpnInfo = { const openVpnInfo = {
macAddress: 0x0000, macAddress: 0x0000,
version: null, version: null,
@@ -327,7 +334,6 @@ const getOpenVpnInfo = async function () {
} }
}; };
/** /**
* Reads system uptime from the proc filesystem asynchronously * Reads system uptime from the proc filesystem asynchronously
*/ */
@@ -1003,13 +1009,16 @@ const parseClientRefresh = function (content, instNum) {
}; };
/** /**
* Check if site-to-site configuration is explicitly turned on * Check if site-to-site configuration
*/ */
const parseSiteToSite = function (content) { const parseSiteToSite = function (content) {
const sitetosite_regex = /^[ \t]*setenv\s+sitetosite\s+on/mi; const sitetosite_regex = /^[ \t]*setenv\s+sitetosite\s+on/mi;
return sitetosite_regex.test(content); return sitetosite_regex.test(content);
} }
/**
* Check if flood guard is turned on
*/
const parseFloodGuard = function (content) { const parseFloodGuard = function (content) {
const sitetosite_regex = /^[ \t]*setenv\s+floodguard\s+on/mi; const sitetosite_regex = /^[ \t]*setenv\s+floodguard\s+on/mi;
return sitetosite_regex.test(content); return sitetosite_regex.test(content);
@@ -1282,7 +1291,6 @@ const getProcessStartTime = async function (id, pidVal, currentUptime) {
return 0; return 0;
}; };
/** /**
* Read the connected client status file if necessary or use the statusFileCache * Read the connected client status file if necessary or use the statusFileCache
*/ */
@@ -51,7 +51,9 @@ const TXT = {
MISSING: _('MISSING'), MISSING: _('MISSING'),
WORKING: _('WORKING'), WORKING: _('WORKING'),
mask: _('Mask'), mask: _('Mask'),
openvpn_profile_valid: _('OpenVPN Profile Valid:'), openvpn_profile_valid: _('OpenVPN Profile Valid'),
client_profile_detected: _('OpenVPN Client Profile detected'),
server_profile_detected: _('OpenVPN Server Profile detected'),
outside_port: _('Outside Port'), outside_port: _('Outside Port'),
port_forwarding: _('Port Forwarding'), port_forwarding: _('Port Forwarding'),
protocol: _('Protocol'), protocol: _('Protocol'),
@@ -261,6 +263,7 @@ const WIZARD_DATA_TEMPLATE = Object.freeze({
importOvpnClientProfileCallback: null, // Function to parse text, key bundles, and routing layouts from uploaded files importOvpnClientProfileCallback: null, // Function to parse text, key bundles, and routing layouts from uploaded files
instanceNumber: 1, // Stores the current or next available numeric instance identification index instanceNumber: 1, // Stores the current or next available numeric instance identification index
forcedScenario: null, // Bypasses the initial role selection step if the scenario context is locked forcedScenario: null, // Bypasses the initial role selection step if the scenario context is locked
forcedScenarioTitle: null // Custrom title for Forced Cenario
}); });
/** /**
@@ -283,7 +286,8 @@ const WIZARD_PARAMS_TEMPLATE = Object.freeze({
isStaticIp: false, // Semiautomatic safety override bypassing the active IP migration checks isStaticIp: false, // Semiautomatic safety override bypassing the active IP migration checks
ddnsProvider: '', // Selected dynamic DNS provider configuration key profile token identification ddnsProvider: '', // Selected dynamic DNS provider configuration key profile token identification
ddnsUrl: '', // Generated secure HTTP GET request string dispatched for provider synchronization ddnsUrl: '', // Generated secure HTTP GET request string dispatched for provider synchronization
importedRawOvpn: '' // Temporary payload staging buffer holding raw client file profile contents importedRawOvpn: '', // Temporary payload staging buffer holding raw client file profile contents
isServerBackup: false,
}); });
@@ -1166,7 +1170,7 @@ const importClientInstance = async function (elements, wizardData, nextBtn, prev
// Path B: Full setup for a brand new profile installation // Path B: Full setup for a brand new profile installation
const wizardParams = Object.assign({}, WIZARD_PARAMS_TEMPLATE, { const wizardParams = Object.assign({}, WIZARD_PARAMS_TEMPLATE, {
clients: [], clients: [],
role: OPENVPN.ROLE.CLIENT, role: extractedParams.isServerBackup ? OPENVPN.ROLE.SERVER : OPENVPN.ROLE.CLIENT,
port: extractedParams.port, port: extractedParams.port,
proto: extractedParams.proto, proto: extractedParams.proto,
displayName: extractedParams.cname || cleanCustomName || TXT.INFO.imported_clients, displayName: extractedParams.cname || cleanCustomName || TXT.INFO.imported_clients,
@@ -1175,7 +1179,8 @@ const importClientInstance = async function (elements, wizardData, nextBtn, prev
remoteServerLan: extractedParams.remoteServerLan, remoteServerLan: extractedParams.remoteServerLan,
isApMode: false, isApMode: false,
ddnsOrPublicIp: '', ddnsOrPublicIp: '',
importedRawOvpn: activeSubParams.importedRawOvpn || '' importedRawOvpn: activeSubParams.importedRawOvpn || '',
isServerBackup: extractedParams.isServerBackup
}); });
if (typeof wizardData.addNewInstanceCallback === 'function') { if (typeof wizardData.addNewInstanceCallback === 'function') {
@@ -1633,14 +1638,17 @@ const renderOptionSiteClient = function (buttons, messageBox) {
const remoteServerMatch = text.match(/^remote\s+(\S+)\s+(\d+)/m); const remoteServerMatch = text.match(/^remote\s+(\S+)\s+(\d+)/m);
const protoMatch = text.match(/^proto\s+(\S+)/m); const protoMatch = text.match(/^proto\s+(\S+)/m);
const hasCaTag = (text.indexOf('<ca>') !== -1); const hasCaTag = (text.indexOf('<ca>') !== -1);
const isServerBackupFile = /^server\s+/m.test(text) || /^server-ipv6\s+/m.test(text);
if (remoteServerMatch) { if (remoteServerMatch) {
// PATH A: Valid full configuration profile (.ovpn) // PATH A: Valid full client configuration profile (.ovpn)
uploadedFileContentStr = text; uploadedFileContentStr = text;
const successContent = E('div', {}, [ const successContent = E('div', {}, [
E('strong', {}, ICON.SUCCESS + ' ' + TXT.INFO.openvpn_profile_valid), E('strong', {}, ICON.SUCCESS + ' ' + TXT.INFO.openvpn_profile_valid),
E('br'), E('br'),
E('span', {}, ICON.ARROW + ' ' + TXT.INFO.client_profile_detected),
E('br'),
E('span', {}, [ E('span', {}, [
ICON.ARROW + ' ' + TXT.INFO.server_address + ': ', ICON.ARROW + ' ' + TXT.INFO.server_address + ': ',
E('code', { 'style': 'font-weight:bold; color:var(--primary-color-high, #1976d2);' }, E('code', { 'style': 'font-weight:bold; color:var(--primary-color-high, #1976d2);' },
@@ -1655,8 +1663,24 @@ const renderOptionSiteClient = function (buttons, messageBox) {
buttons.next.disabled = false; buttons.next.disabled = false;
} }
} }
else if (isServerBackupFile === true) {
// PATH B: Valid full server configuration profile (.ovpn)
uploadedFileContentStr = text;
const successContent = E('div', {}, [
E('strong', {}, ICON.SUCCESS + ' ' + TXT.INFO.openvpn_profile_valid),
E('br'),
E('span', {}, ICON.ARROW + ' ' + TXT.INFO.server_profile_detected),
]);
showMessage(successContent, MESSAGE_TYPE.OK, messageBox);
if (buttons && buttons.next) {
buttons.next.disabled = false;
}
}
else if (isCrtExtension === true || (hasCaTag === true && !remoteServerMatch)) { else if (isCrtExtension === true || (hasCaTag === true && !remoteServerMatch)) {
// PATH B: Valid cryptographic multi-key bundle (.crt) // PATH C: Valid cryptographic multi-key bundle (.crt)
uploadedFileContentStr = text; uploadedFileContentStr = text;
const warningContent = E('div', {}, [ const warningContent = E('div', {}, [
@@ -1677,7 +1701,7 @@ const renderOptionSiteClient = function (buttons, messageBox) {
} }
} }
else { else {
// PATH C: File is corrupted or missing vital fields // PATH D: File is corrupted or missing vital fields
fileInput.classList.add('cbi-input-invalid'); fileInput.classList.add('cbi-input-invalid');
customUploadBtn.classList.add('cbi-input-invalid'); customUploadBtn.classList.add('cbi-input-invalid');
fileInput.value = ''; fileInput.value = '';
@@ -2215,7 +2239,11 @@ const updateStepVisibility = function (elements, state, buttons, wizardData) {
scenarioTitle += '\u2003\u2003' + ICON.TAG + elements.inputs.displayName.value; scenarioTitle += '\u2003\u2003' + ICON.TAG + elements.inputs.displayName.value;
} }
state.nodes.descr.textContent = scenarioTitle; if (wizardData.forcedScenarioTitle) {
state.nodes.descr.textContent = '';
} else {
state.nodes.descr.textContent = scenarioTitle;
}
} }
}; };
@@ -2815,7 +2843,6 @@ const setupAddressValidator = function (elements, buttons, portForwardingAlert,
}); });
}; };
/** /**
* MAIN WIZARD * MAIN WIZARD
*/ */
@@ -2870,8 +2897,15 @@ const openWizardModal = function (wizardData, optionalHideClient) {
document.head.appendChild(styleNode); document.head.appendChild(styleNode);
} }
let title;
if (wizardData.forcedScenarioTitle) {
title = wizardData.forcedScenarioTitle
} else {
title = ICON.ROCKET + TXT.INFO.setup_wizard;
}
// Show the modal window on the screen with a simple layout structure // Show the modal window on the screen with a simple layout structure
L.ui.showModal(ICON.ROCKET + TXT.INFO.setup_wizard, [ L.ui.showModal(title, [
E('div', { E('div', {
'class': 'cbi-map ovpn-plus-setup-wizard', 'class': 'cbi-map ovpn-plus-setup-wizard',
'style': 'width: 100%; margin: 0 auto;' 'style': 'width: 100%; margin: 0 auto;'
@@ -2898,12 +2932,12 @@ const openWizardModal = function (wizardData, optionalHideClient) {
/** /**
* Export the open wizard modal function to LuCI * Export the open wizard modal function to LuCI
*/ */
return L.Class.extend( return L.Class.extend({
{ WIZARD_DATA_TEMPLATE: WIZARD_DATA_TEMPLATE,
WIZARD_DATA_TEMPLATE: WIZARD_DATA_TEMPLATE, WIZARD_PARAMS_TEMPLATE: WIZARD_PARAMS_TEMPLATE,
WIZARD_PARAMS_TEMPLATE: WIZARD_PARAMS_TEMPLATE, renderSubnetInputs: renderSubnetInputs,
renderSubnetInputs: renderSubnetInputs, renderPortForwardingAlert: renderPortForwardingAlert,
renderPortForwardingAlert: renderPortForwardingAlert, getValidCommonName: getValidCommonName,
getValidCommonName: getValidCommonName, openWizardModal: openWizardModal
openWizardModal: openWizardModal });
});
File diff suppressed because it is too large. Load diff
@@ -129,6 +129,36 @@ apply_dns_forward() {
echo "$dns_mode" > "$DNS_MODE_FILE" echo "$dns_mode" > "$DNS_MODE_FILE"
} }
# Run 'tailscale set' and recover from one known failure: a daemon built
# without auto-update support (ts_omit_clientupdate) rejects every edit whose
# result still has AutoUpdate.Apply=true, so while that flag is saved none of
# the settings below can be applied. Reset it once and retry. Any other
# failure, and the result of the retry, is reported unchanged.
run_tailscale_set() {
local ts_bin="$1" err ret
shift
err=$("$ts_bin" "$@" 2>&1)
ret=$?
if [ $ret -ne 0 ]; then
case "$err" in
*'Auto-update support is disabled in this build'*)
logger -t "$NAME" "AutoUpdate.Apply=true is unsupported in this build, resetting it"
if "$ts_bin" set --auto-update=false; then
err=$("$ts_bin" "$@" 2>&1)
ret=$?
else
logger -t "$NAME" "Failed to reset auto-update"
fi
;;
esac
fi
[ -z "$err" ] || printf '%s\n' "$err" >&2
return $ret
}
apply_settings() { apply_settings() {
local ts_bin local ts_bin
if [ -x /usr/sbin/tailscale ]; then if [ -x /usr/sbin/tailscale ]; then
@@ -253,7 +283,7 @@ apply_settings() {
fi fi
logger -t "$NAME" "Applying settings" logger -t "$NAME" "Applying settings"
"$ts_bin" "$@" run_tailscale_set "$ts_bin" "$@"
local ret=$? local ret=$?
[ $ret -eq 0 ] \ [ $ret -eq 0 ] \
&& logger -t "$NAME" "Settings applied successfully" \ && logger -t "$NAME" "Settings applied successfully" \
+1 -1
View File
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +USE_APK:wget-any +!USE_APK:wget +jsonfilter +ucode-mod
# Keep existing translation package names and the argon-config message domain. # Keep existing translation package names and the argon-config message domain.
LUCI_BASENAME:=argon-config LUCI_BASENAME:=argon-config
PKG_VERSION:=3.1.0 PKG_VERSION:=3.1.0
PKG_RELEASE:=32 PKG_RELEASE:=33
PKG_LICENSE:=Apache-2.0 MIT PKG_LICENSE:=Apache-2.0 MIT
# LuCI derives translations independently of the theme version. Keep release # LuCI derives translations independently of the theme version. Keep release
@@ -2107,7 +2107,6 @@ span.spinning {
#cbi-wireless > .cbi-section-node, #cbi-wireless > .cbi-section-node,
#cbi-wireless > #wifi_assoclist_table, #cbi-wireless > #wifi_assoclist_table,
[data-tab-title], [data-tab-title],
[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear),
[data-page="admin-system-opkg"] #maincontent > .container { [data-page="admin-system-opkg"] #maincontent > .container {
font-family: inherit; font-family: inherit;
font-weight: normal; font-weight: normal;
@@ -2128,7 +2127,6 @@ span.spinning {
#cbi-wireless > .cbi-section-node:last-child, #cbi-wireless > .cbi-section-node:last-child,
#cbi-wireless > #wifi_assoclist_table:last-child, #cbi-wireless > #wifi_assoclist_table:last-child,
[data-tab-title]:last-child, [data-tab-title]:last-child,
[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear):last-child,
[data-page="admin-system-opkg"] #maincontent > .container:last-child { [data-page="admin-system-opkg"] #maincontent > .container:last-child {
margin: 0; margin: 0;
border: 0; border: 0;
@@ -4777,6 +4775,57 @@ pre.command-output {
gap: var(--control-gap); gap: var(--control-gap);
padding: 0 var(--section-inset) var(--field-gap); padding: 0 var(--section-inset) var(--field-gap);
} }
[data-page="admin-system-admin"] .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed):where(:has(> .cbi-value, > [id] > .cbi-value)),
[data-page="admin-system-admin-password"] .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed):where(:has(> .cbi-value, > [id] > .cbi-value)),
[data-page="admin-system-admin-pwpolicy"] .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed):where(:has(> .cbi-value, > [id] > .cbi-value)),
[data-page="admin-system-admin-uhttpd"] .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed):where(:has(> .cbi-value, > [id] > .cbi-value)) {
padding-block: var(--field-gap);
}
[data-page="admin-system-admin"] .cbi-section > .cbi-section-descr:not(:empty) ~ .cbi-section-node:not(.cbi-section-node-tabbed),
[data-page="admin-system-admin-password"] .cbi-section > .cbi-section-descr:not(:empty) ~ .cbi-section-node:not(.cbi-section-node-tabbed),
[data-page="admin-system-admin-pwpolicy"] .cbi-section > .cbi-section-descr:not(:empty) ~ .cbi-section-node:not(.cbi-section-node-tabbed),
[data-page="admin-system-admin-uhttpd"] .cbi-section > .cbi-section-descr:not(:empty) ~ .cbi-section-node:not(.cbi-section-node-tabbed) {
padding-block-start: 0;
}
[data-page="admin-system-admin"] .cbi-section > .cbi-value,
[data-page="admin-system-admin-password"] .cbi-section > .cbi-value,
[data-page="admin-system-admin-pwpolicy"] .cbi-section > .cbi-value,
[data-page="admin-system-admin-uhttpd"] .cbi-section > .cbi-value {
padding-block: var(--field-gap);
}
[data-page="admin-system-admin"] .cbi-section > .cbi-value ~ .cbi-value,
[data-page="admin-system-admin-password"] .cbi-section > .cbi-value ~ .cbi-value,
[data-page="admin-system-admin-pwpolicy"] .cbi-section > .cbi-value ~ .cbi-value,
[data-page="admin-system-admin-uhttpd"] .cbi-section > .cbi-value ~ .cbi-value,
[data-page="admin-system-admin"] .cbi-section > .cbi-section-descr:not(:empty) ~ .cbi-value,
[data-page="admin-system-admin-password"] .cbi-section > .cbi-section-descr:not(:empty) ~ .cbi-value,
[data-page="admin-system-admin-pwpolicy"] .cbi-section > .cbi-section-descr:not(:empty) ~ .cbi-value,
[data-page="admin-system-admin-uhttpd"] .cbi-section > .cbi-section-descr:not(:empty) ~ .cbi-value {
padding-block-start: 0;
}
[data-page="admin-system-admin"] .cbi-section > .cbi-section-create,
[data-page="admin-system-admin-password"] .cbi-section > .cbi-section-create,
[data-page="admin-system-admin-pwpolicy"] .cbi-section > .cbi-section-create,
[data-page="admin-system-admin-uhttpd"] .cbi-section > .cbi-section-create {
padding: var(--field-gap) var(--section-inset);
}
[data-page="admin-system-admin-pwpolicy"] #view > .cbi-map {
min-width: 0;
border-radius: var(--radius-card);
background-color: var(--surface-color);
box-shadow: var(--section-shadow);
gap: 0;
}
[data-page="admin-system-admin-pwpolicy"] #view > .cbi-map > h2 {
margin: 0;
border-end-start-radius: 0;
border-end-end-radius: 0;
box-shadow: none;
}
[data-page="admin-system-admin-pwpolicy"] #view > .cbi-map > .cbi-section {
background: none;
box-shadow: none;
}
[data-page="admin-system-admin"] .cbi-map h2, [data-page="admin-system-admin"] .cbi-map h2,
[data-page="admin-system-admin-password"] .cbi-map h2, [data-page="admin-system-admin-password"] .cbi-map h2,
[data-page="admin-system-admin"] .cbi-map .cbi-map-descr, [data-page="admin-system-admin"] .cbi-map .cbi-map-descr,
@@ -4800,10 +4849,17 @@ pre.command-output {
[data-page="admin-system-admin-password"] [data-name="pw2"] .cbi-value-description span[style="color:green"] { [data-page="admin-system-admin-password"] [data-name="pw2"] .cbi-value-description span[style="color:green"] {
color: var(--strength-strong-text) !important; color: var(--strength-strong-text) !important;
} }
[data-page="admin-system-admin-sshkeys"] #view > div:has(> .cbi-section-node > .cbi-dynlist) > .cbi-section-node {
min-width: 0;
border-radius: var(--radius-card);
background-color: var(--surface-color);
box-shadow: var(--section-shadow);
padding: var(--field-gap) var(--section-inset);
}
[data-page="admin-system-admin-sshkeys"] .cbi-dynlist { [data-page="admin-system-admin-sshkeys"] .cbi-dynlist {
width: 100%; width: 100%;
margin-inline: var(--section-inset); margin: 0;
max-width: min(48rem, calc(100% - 2 * var(--section-inset))); max-width: min(48rem, 100%);
gap: var(--field-gap); gap: var(--field-gap);
} }
[data-page="admin-system-admin-sshkeys"] .cbi-dynlist > .item { [data-page="admin-system-admin-sshkeys"] .cbi-dynlist > .item {
@@ -4927,6 +4983,23 @@ pre.command-output {
overflow-wrap: anywhere; overflow-wrap: anywhere;
user-select: text; user-select: text;
} }
[data-page="admin-system-admin-repokeys"] .cbi-section-table {
border-collapse: separate;
border-spacing: 0;
border-radius: inherit;
}
[data-page="admin-system-admin-repokeys"] .cbi-section-table > .thead,
[data-page="admin-system-admin-repokeys"] .cbi-section-table > .thead > .tr {
border-start-start-radius: inherit;
border-start-end-radius: inherit;
background: var(--surface-muted);
}
[data-page="admin-system-admin-repokeys"] .cbi-section-table > .thead > .tr > .th:first-child {
border-start-start-radius: inherit;
}
[data-page="admin-system-admin-repokeys"] .cbi-section-table .cbi-section-table-row {
margin-block: 0;
}
[data-page="admin-system-admin-repokeys"] textarea { [data-page="admin-system-admin-repokeys"] textarea {
width: 100%; width: 100%;
min-width: 0; min-width: 0;
@@ -5210,6 +5283,44 @@ pre.command-output {
object-fit: contain; object-fit: contain;
flex-shrink: 0; flex-shrink: 0;
} }
[data-page="admin-system-package-manager"] #view > #packages,
[data-page="admin-system-opkg"] #view > #packages {
min-width: 0;
border-radius: var(--radius-card);
background-color: var(--surface-color);
box-shadow: var(--section-shadow);
border-collapse: separate;
border-spacing: 0;
overflow: visible;
}
[data-page="admin-system-package-manager"] #view > #packages > :is(thead, tbody, .thead, .tbody):first-child,
[data-page="admin-system-opkg"] #view > #packages > :is(thead, tbody, .thead, .tbody):first-child,
[data-page="admin-system-package-manager"] #view > #packages > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child,
[data-page="admin-system-opkg"] #view > #packages > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child,
[data-page="admin-system-package-manager"] #view > #packages > :is(tr, .tr):first-child,
[data-page="admin-system-opkg"] #view > #packages > :is(tr, .tr):first-child {
border-start-start-radius: inherit;
border-start-end-radius: inherit;
}
[data-page="admin-system-package-manager"] #view > #packages > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child > :is(td, th, .td, .th):first-child,
[data-page="admin-system-opkg"] #view > #packages > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child > :is(td, th, .td, .th):first-child,
[data-page="admin-system-package-manager"] #view > #packages > :is(tr, .tr):first-child > :is(td, th, .td, .th):first-child,
[data-page="admin-system-opkg"] #view > #packages > :is(tr, .tr):first-child > :is(td, th, .td, .th):first-child {
border-start-start-radius: inherit;
}
[data-page="admin-system-package-manager"] #view > #packages > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child > :is(td, th, .td, .th):last-child,
[data-page="admin-system-opkg"] #view > #packages > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child > :is(td, th, .td, .th):last-child,
[data-page="admin-system-package-manager"] #view > #packages > :is(tr, .tr):first-child > :is(td, th, .td, .th):last-child,
[data-page="admin-system-opkg"] #view > #packages > :is(tr, .tr):first-child > :is(td, th, .td, .th):last-child {
border-start-end-radius: inherit;
}
@media screen and (max-width: 1152px) {
[data-page="admin-system-package-manager"] #view > #packages :is(.table-titles, .cbi-section-table-titles) + .tr,
[data-page="admin-system-opkg"] #view > #packages :is(.table-titles, .cbi-section-table-titles) + .tr {
border-start-start-radius: inherit;
border-start-end-radius: inherit;
}
}
[data-page="admin-system-package-manager"] .controls, [data-page="admin-system-package-manager"] .controls,
[data-page="admin-system-opkg"] .controls { [data-page="admin-system-opkg"] .controls {
min-width: 0; min-width: 0;
@@ -5334,7 +5445,17 @@ pre.command-output {
flex-basis: 50%; flex-basis: 50%;
} }
} }
[data-page="admin-system-crontab"] #view > p:has(> textarea) {
min-width: 0;
border-radius: var(--radius-card);
background-color: var(--surface-color);
box-shadow: var(--section-shadow);
margin: 0;
padding: var(--field-gap) var(--section-inset);
line-height: 0;
}
[data-page="admin-system-crontab"] #view p textarea { [data-page="admin-system-crontab"] #view p textarea {
display: block;
direction: ltr; direction: ltr;
unicode-bidi: plaintext; unicode-bidi: plaintext;
text-align: start; text-align: start;
@@ -6043,6 +6164,9 @@ pre.command-output {
top: 0.1rem; top: 0.1rem;
left: 1rem; left: 1rem;
} }
[data-page="admin-system-reboot"] #view > hr {
display: none;
}
[data-page="admin-system-reboot"] .cbi-button { [data-page="admin-system-reboot"] .cbi-button {
background: var(--warning-action-bg) !important; background: var(--warning-action-bg) !important;
border-color: var(--warning-action-bg) !important; border-color: var(--warning-action-bg) !important;
@@ -6313,6 +6437,14 @@ pre.command-output {
direction: ltr; direction: ltr;
unicode-bidi: isolate; unicode-bidi: isolate;
} }
[data-page="admin-network-diagnostics"] #view > .cbi-map > .table {
min-width: 0;
border-radius: var(--radius-card);
background-color: var(--surface-color);
box-shadow: var(--section-shadow);
padding: var(--control-gap);
border-collapse: separate;
}
[data-page="admin-network-diagnostics"] .table { [data-page="admin-network-diagnostics"] .table {
overflow: visible; overflow: visible;
box-shadow: none; box-shadow: none;
@@ -6339,7 +6471,7 @@ pre.command-output {
} }
[data-page="admin-network-diagnostics"] .tr .td { [data-page="admin-network-diagnostics"] .tr .td {
background-color: var(--surface-color) !important; background-color: var(--surface-color) !important;
border-bottom: 1px solid var(--border-color) !important; border-bottom: 0 !important;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
@@ -6468,6 +6600,35 @@ pre.command-output {
#view > .Dashboard [data-tab="layout"] .cbi-page-actions { #view > .Dashboard [data-tab="layout"] .cbi-page-actions {
padding: var(--field-gap) var(--section-inset); padding: var(--field-gap) var(--section-inset);
} }
[data-page="admin-status-processes"] #view > .table {
min-width: 0;
border-radius: var(--radius-card);
background-color: var(--surface-color);
box-shadow: var(--section-shadow);
border-collapse: separate;
border-spacing: 0;
overflow: visible;
}
[data-page="admin-status-processes"] #view > .table > :is(thead, tbody, .thead, .tbody):first-child,
[data-page="admin-status-processes"] #view > .table > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child,
[data-page="admin-status-processes"] #view > .table > :is(tr, .tr):first-child {
border-start-start-radius: inherit;
border-start-end-radius: inherit;
}
[data-page="admin-status-processes"] #view > .table > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child > :is(td, th, .td, .th):first-child,
[data-page="admin-status-processes"] #view > .table > :is(tr, .tr):first-child > :is(td, th, .td, .th):first-child {
border-start-start-radius: inherit;
}
[data-page="admin-status-processes"] #view > .table > :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child > :is(td, th, .td, .th):last-child,
[data-page="admin-status-processes"] #view > .table > :is(tr, .tr):first-child > :is(td, th, .td, .th):last-child {
border-start-end-radius: inherit;
}
@media screen and (max-width: 1152px) {
[data-page="admin-status-processes"] #view > .table :is(.table-titles, .cbi-section-table-titles) + .tr {
border-start-start-radius: inherit;
border-start-end-radius: inherit;
}
}
[data-page="admin-status-processes"] .th:nth-child(3), [data-page="admin-status-processes"] .th:nth-child(3),
[data-page="admin-status-processes"] .td:nth-child(3) { [data-page="admin-status-processes"] .td:nth-child(3) {
text-align: start; text-align: start;
@@ -7013,9 +7174,35 @@ pre.command-output {
[data-page="admin-status-nftables"] .alert-message.fade-out { [data-page="admin-status-nftables"] .alert-message.fade-out {
opacity: 0; opacity: 0;
} }
[data-page="admin-status-logs"] #content_syslog,
[data-page="admin-status-logs-syslog"] #content_syslog,
[data-page="admin-status-logs-dmesg"] #content_syslog {
min-width: 0;
border-radius: var(--radius-card);
background-color: var(--surface-color);
box-shadow: var(--section-shadow);
display: flex;
flex-direction: column;
gap: var(--field-gap);
padding: var(--field-gap) var(--section-inset);
}
[data-page="admin-status-logs"] #content_syslog > div,
[data-page="admin-status-logs-syslog"] #content_syslog > div,
[data-page="admin-status-logs-dmesg"] #content_syslog > div {
margin-block: 0 !important;
}
[data-page="admin-status-logs"] #content_syslog > div:has(> .cbi-button),
[data-page="admin-status-logs-syslog"] #content_syslog > div:has(> .cbi-button),
[data-page="admin-status-logs-dmesg"] #content_syslog > div:has(> .cbi-button) {
padding-block: 0 !important;
}
[data-page="admin-status-logs"] #syslog, [data-page="admin-status-logs"] #syslog,
[data-page="admin-status-logs-syslog"] #syslog, [data-page="admin-status-logs-syslog"] #syslog,
[data-page="admin-status-logs-dmesg"] #syslog { [data-page="admin-status-logs-dmesg"] #syslog {
display: block;
margin: 0;
border: 1px solid var(--control-border);
background-color: var(--input-bg);
direction: ltr; direction: ltr;
unicode-bidi: plaintext; unicode-bidi: plaintext;
text-align: start; text-align: start;
@@ -7027,7 +7214,7 @@ pre.command-output {
[data-page="admin-status-logs-syslog"] #content_syslog > div:has(> input, > select), [data-page="admin-status-logs-syslog"] #content_syslog > div:has(> input, > select),
[data-page="admin-status-logs-dmesg"] #content_syslog > div:has(> input, > select) { [data-page="admin-status-logs-dmesg"] #content_syslog > div:has(> input, > select) {
line-height: 2.5rem; line-height: 2.5rem;
margin-block-end: var(--control-gap) !important; margin-block-end: 0 !important;
} }
[data-page="admin-status-logs"] #content_syslog > div:has(> input, > select) > label, [data-page="admin-status-logs"] #content_syslog > div:has(> input, > select) > label,
[data-page="admin-status-logs-syslog"] #content_syslog > div:has(> input, > select) > label, [data-page="admin-status-logs-syslog"] #content_syslog > div:has(> input, > select) > label,
@@ -7119,6 +7306,29 @@ pre.command-output {
[data-page="admin-services-nlbw-display"] .cbi-section > .right { [data-page="admin-services-nlbw-display"] .cbi-section > .right {
margin: 15px 15px 0px 0px; margin: 15px 15px 0px 0px;
} }
[data-page="admin-services-wol"] #cbi-luci-wol-defaults-_info {
padding-bottom: var(--field-gap);
line-height: 1.5;
}
[data-page="admin-services-wol"] #cbi-luci-wol-defaults-_info > output {
width: 100%;
min-width: 0;
}
[data-page="admin-services-wol"] #cbi-luci-wol-defaults-_info output p {
padding: 0;
}
[data-page="admin-services-wol"] #cbi-luci-wol-defaults-_info .control-group {
margin-block-start: var(--control-gap);
}
[data-page="admin-services-wol"] .cbi-section:has(> #wol-output-text) {
padding-bottom: var(--field-gap);
}
[data-page="admin-services-wol"] #wol-output-text {
display: block;
width: calc(100% - var(--section-inset) - var(--section-inset)) !important;
margin-inline: var(--section-inset);
resize: vertical;
}
[data-page="admin-services-ddns"] #syslog { [data-page="admin-services-ddns"] #syslog {
overflow: auto; overflow: auto;
font-size: 12px !important; font-size: 12px !important;
@@ -7205,7 +7415,7 @@ pre.command-output {
--oc-text: var(--text-color); --oc-text: var(--text-color);
--oc-text-muted: var(--plugin-text-muted); --oc-text-muted: var(--plugin-text-muted);
--oc-neutral: var(--plugin-neutral); --oc-neutral: var(--plugin-neutral);
--oc-scrollbar: var(--plugin-scrollbar); --oc-scrollbar: var(--oc-accent);
} }
[data-page^="admin-services-openclash"] img[src$="arrow-clockwise-light.svg"], [data-page^="admin-services-openclash"] img[src$="arrow-clockwise-light.svg"],
[data-page^="admin-services-openclash"] img[src$="wrench-light.svg"], [data-page^="admin-services-openclash"] img[src$="wrench-light.svg"],
@@ -7256,8 +7466,8 @@ pre.command-output {
--primary-a60: var(--oc-accent-a60) !important; --primary-a60: var(--oc-accent-a60) !important;
--radio-hover-bg: var(--oc-accent-a10) !important; --radio-hover-bg: var(--oc-accent-a10) !important;
--tab-hover-dark-bg: var(--oc-accent-a10) !important; --tab-hover-dark-bg: var(--oc-accent-a10) !important;
--scrollbar-thumb: var(--oc-neutral) !important; --scrollbar-thumb: var(--oc-scrollbar) !important;
--scrollbar-thumb-hover: var(--oc-text-muted) !important; --scrollbar-thumb-hover: var(--oc-accent-hover) !important;
border-radius: var(--radius-card); border-radius: var(--radius-card);
} }
[data-page^="admin-services-openclash"] .oc .developer-container, [data-page^="admin-services-openclash"] .oc .developer-container,
@@ -7271,6 +7481,22 @@ pre.command-output {
[data-page^="admin-services-openclash"] .oc .config-upload-content { [data-page^="admin-services-openclash"] .oc .config-upload-content {
background: var(--oc-surface) !important; background: var(--oc-surface) !important;
} }
[data-page^="admin-services-openclash"] textarea::-webkit-scrollbar-thumb,
[data-page^="admin-services-openclash"] .oc::-webkit-scrollbar-thumb,
[data-page^="admin-services-openclash"] .oc *::-webkit-scrollbar-thumb,
[data-page^="admin-services-openclash"] .oc .cm-editor .cm-scroller::-webkit-scrollbar-thumb,
[data-page^="admin-services-openclash"] .oc #debug-rendered::-webkit-scrollbar-thumb,
[data-page^="admin-services-openclash"] .oc #debug-rendered pre::-webkit-scrollbar-thumb {
background-color: var(--oc-scrollbar) !important;
}
[data-page^="admin-services-openclash"] textarea::-webkit-scrollbar-thumb:hover,
[data-page^="admin-services-openclash"] .oc::-webkit-scrollbar-thumb:hover,
[data-page^="admin-services-openclash"] .oc *::-webkit-scrollbar-thumb:hover,
[data-page^="admin-services-openclash"] .oc .cm-editor .cm-scroller::-webkit-scrollbar-thumb:hover,
[data-page^="admin-services-openclash"] .oc #debug-rendered::-webkit-scrollbar-thumb:hover,
[data-page^="admin-services-openclash"] .oc #debug-rendered pre::-webkit-scrollbar-thumb:hover {
background-color: var(--oc-accent-hover) !important;
}
[data-page^="admin-services-openclash"] .cbi-button.cbi-button-add, [data-page^="admin-services-openclash"] .cbi-button.cbi-button-add,
[data-page^="admin-services-openclash"] .cbi-button.cbi-button-apply, [data-page^="admin-services-openclash"] .cbi-button.cbi-button-apply,
[data-page^="admin-services-openclash"] .cbi-button.cbi-button-edit, [data-page^="admin-services-openclash"] .cbi-button.cbi-button-edit,
@@ -7435,6 +7661,7 @@ pre.command-output {
[data-page^="admin-services-openclash"] .oc .config-upload-btn-large:hover { [data-page^="admin-services-openclash"] .oc .config-upload-btn-large:hover {
transform: none !important; transform: none !important;
} }
[data-page^="admin-services-openclash"] b.oc-txt-good,
[data-page^="admin-services-openclash"] b[style*="color:green"], [data-page^="admin-services-openclash"] b[style*="color:green"],
[data-page^="admin-services-openclash"] b[style*="color: green"], [data-page^="admin-services-openclash"] b[style*="color: green"],
[data-page^="admin-services-openclash"] b[color="green"] { [data-page^="admin-services-openclash"] b[color="green"] {
@@ -7469,6 +7696,19 @@ pre.command-output {
padding-top: 0.4375rem; padding-top: 0.4375rem;
padding-bottom: 0.4375rem; padding-bottom: 0.4375rem;
} }
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-settings"] [id="container.openclash.config.version_update"] {
padding-inline: var(--section-inset);
}
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-settings"] [id="container.openclash.config.version_update"] > .oc {
border-radius: 0;
overflow: visible;
}
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-settings"] [id="container.openclash.config.version_update"] #selectPopup.select-popup-tab {
border: 0;
border-radius: 0;
padding: 0;
background: transparent;
}
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-config"] .cbi-map > .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed), [data-page^="admin-services-openclash"][data-page="admin-services-openclash-config"] .cbi-map > .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed),
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-config-subscribe"] .cbi-map > .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed), [data-page^="admin-services-openclash"][data-page="admin-services-openclash-config-subscribe"] .cbi-map > .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed),
[data-page^="admin-services-openclash"][data-page^="admin-services-openclash-custom-dns-edit"] .cbi-map > .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed), [data-page^="admin-services-openclash"][data-page^="admin-services-openclash-custom-dns-edit"] .cbi-map > .cbi-section > .cbi-section-node:not(.cbi-section-node-tabbed),
@@ -7592,6 +7832,35 @@ pre.command-output {
[data-page^="admin-services-openclash"] #tab-content .dom { [data-page^="admin-services-openclash"] #tab-content .dom {
padding: 0 1rem 1rem 1rem; padding: 0 1rem 1rem 1rem;
} }
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-log"] #tab.oc {
height: auto;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-log"] #tab.oc > #tab-header {
padding: var(--field-gap) 0;
border: 0;
background: transparent;
}
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-log"] #tab.oc > #tab-header > ul.oc-tabmenu {
background: transparent;
}
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-log"] #tab.oc + .oc {
border-radius: 0;
}
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-log"] #tab-content > .dom {
padding: 0;
}
[data-page^="admin-services-openclash"][data-page="admin-services-openclash-log"] .oc .select-popup-footer.log-footer {
margin: 0;
padding: var(--field-gap) 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
[data-page^="admin-services-openclash"] #cbi-openclash-dns_servers .cbi-section-table-row > .cbi-section-table-cell:last-child { [data-page^="admin-services-openclash"] #cbi-openclash-dns_servers .cbi-section-table-row > .cbi-section-table-cell:last-child {
min-width: 7.25rem; min-width: 7.25rem;
white-space: nowrap; white-space: nowrap;
@@ -1,5 +1,13 @@
// out: false // out: false
// Opt-in surface for native views that do not render a cbi-section.
.native-page-card() {
min-width: 0;
border-radius: var(--radius-card);
background-color: var(--surface-color);
box-shadow: var(--section-shadow);
}
.cbi-section, .cbi-section,
.cbi-section-error, .cbi-section-error,
#iptables, #iptables,
@@ -8,7 +16,6 @@
#cbi-wireless>.cbi-section-node, #cbi-wireless>.cbi-section-node,
#cbi-wireless>#wifi_assoclist_table, #cbi-wireless>#wifi_assoclist_table,
[data-tab-title], [data-tab-title],
[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear),
[data-page="admin-system-opkg"] #maincontent>.container { [data-page="admin-system-opkg"] #maincontent>.container {
font-family: inherit; font-family: inherit;
font-weight: normal; font-weight: normal;
@@ -1,5 +1,40 @@
// out: false // out: false
// Standalone native tables have no section to supply their surface or corners.
// Round painted cells instead of clipping interactive descendants.
.native-table-card() {
.native-page-card();
border-collapse: separate;
border-spacing: 0;
overflow: visible;
> :is(thead, tbody, .thead, .tbody):first-child,
> :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child,
> :is(tr, .tr):first-child {
border-start-start-radius: inherit;
border-start-end-radius: inherit;
}
> :is(thead, tbody, .thead, .tbody):first-child > :is(tr, .tr):first-child > :is(td, th, .td, .th),
> :is(tr, .tr):first-child > :is(td, th, .td, .th) {
&:first-child {
border-start-start-radius: inherit;
}
&:last-child {
border-start-end-radius: inherit;
}
}
@media screen and (max-width: 1152px) {
// Responsive tables hide their title row; the first data row owns the top.
:is(.table-titles, .cbi-section-table-titles) + .tr {
border-start-start-radius: inherit;
border-start-end-radius: inherit;
}
}
}
// table // table
.table { .table {
+2
View File
@@ -6,6 +6,7 @@
@import "pages/autoreboot.less"; @import "pages/autoreboot.less";
@import "pages/system.less"; @import "pages/system.less";
@import "pages/syscontrol.less"; @import "pages/syscontrol.less";
@import "pages/administration.less";
@import "pages/password.less"; @import "pages/password.less";
@import "pages/sshkeys.less"; @import "pages/sshkeys.less";
@import "pages/repokeys.less"; @import "pages/repokeys.less";
@@ -46,6 +47,7 @@
@import "pages/nlbwmon.less"; @import "pages/nlbwmon.less";
// Services // Services
@import "pages/wol.less";
@import "pages/ddns.less"; @import "pages/ddns.less";
@import "pages/ttyd.less"; @import "pages/ttyd.less";
@import "pages/dockerman.less"; @import "pages/dockerman.less";
@@ -0,0 +1,31 @@
// out: false
// Match native LuCI maps: a heading, an unpainted page description, then
// section cards. Untabbed forms need the inset already supplied by tabs.
[data-page="admin-system-admin"],
[data-page="admin-system-admin-password"],
[data-page="admin-system-admin-pwpolicy"],
[data-page="admin-system-admin-uhttpd"] {
.legacy-cbi-section-spacing();
}
// Password policy has no page description: join its heading and sole form
// into one surface instead of separating two adjacent cards.
[data-page="admin-system-admin-pwpolicy"] {
#view > .cbi-map {
.native-page-card();
gap: 0;
> h2 {
margin: 0;
border-end-start-radius: 0;
border-end-end-radius: 0;
box-shadow: none;
}
> .cbi-section {
background: none;
box-shadow: none;
}
}
}
+8
View File
@@ -2,8 +2,16 @@
// System - Crontab // System - Crontab
[data-page="admin-system-crontab"] { [data-page="admin-system-crontab"] {
#view > p:has(> textarea) {
.native-page-card();
margin: 0;
padding: var(--field-gap) var(--section-inset);
line-height: 0;
}
#view p { #view p {
textarea { textarea {
display: block;
direction: ltr; direction: ltr;
unicode-bidi: plaintext; unicode-bidi: plaintext;
text-align: start; text-align: start;
+7 -1
View File
@@ -2,6 +2,12 @@
// Network - Diagnostics // Network - Diagnostics
[data-page="admin-network-diagnostics"] { [data-page="admin-network-diagnostics"] {
#view > .cbi-map > .table {
.native-page-card();
padding: var(--control-gap);
border-collapse: separate;
}
.table { .table {
// The toolbar must not become the dropdown's scroll/size boundary. // The toolbar must not become the dropdown's scroll/size boundary.
overflow: visible; overflow: visible;
@@ -32,7 +38,7 @@
.td { .td {
background-color: var(--surface-color) !important; background-color: var(--surface-color) !important;
border-bottom: 1px solid var(--border-color) !important; border-bottom: 0 !important;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
+22 -1
View File
@@ -3,7 +3,28 @@
[data-page="admin-status-logs"], [data-page="admin-status-logs"],
[data-page="admin-status-logs-syslog"], [data-page="admin-status-logs-syslog"],
[data-page="admin-status-logs-dmesg"] { [data-page="admin-status-logs-dmesg"] {
#content_syslog {
.native-page-card();
display: flex;
flex-direction: column;
gap: var(--field-gap);
padding: var(--field-gap) var(--section-inset);
> div {
margin-block: 0 !important;
}
// LuCI uses padded wrappers for the two scroll buttons.
> div:has(> .cbi-button) {
padding-block: 0 !important;
}
}
#syslog { #syslog {
display: block;
margin: 0;
border: 1px solid var(--control-border);
background-color: var(--input-bg);
direction: ltr; direction: ltr;
unicode-bidi: plaintext; unicode-bidi: plaintext;
text-align: start; text-align: start;
@@ -18,7 +39,7 @@
[data-page="admin-status-logs-dmesg"] { [data-page="admin-status-logs-dmesg"] {
#content_syslog > div:has(> input, > select) { #content_syslog > div:has(> input, > select) {
line-height: 2.5rem; line-height: 2.5rem;
margin-block-end: var(--control-gap) !important; margin-block-end: 0 !important;
> label, > label,
> input, > input,
+80 -3
View File
@@ -27,7 +27,7 @@
--oc-text: var(--text-color); --oc-text: var(--text-color);
--oc-text-muted: var(--plugin-text-muted); --oc-text-muted: var(--plugin-text-muted);
--oc-neutral: var(--plugin-neutral); --oc-neutral: var(--plugin-neutral);
--oc-scrollbar: var(--plugin-scrollbar); --oc-scrollbar: var(--oc-accent);
@supports (color: color-mix(in srgb, red, blue)) { @supports (color: color-mix(in srgb, red, blue)) {
--oc-accent-hover: color-mix(in srgb, var(--oc-accent) 88%, black); --oc-accent-hover: color-mix(in srgb, var(--oc-accent) 88%, black);
@@ -72,8 +72,8 @@
--primary-a60: var(--oc-accent-a60) !important; --primary-a60: var(--oc-accent-a60) !important;
--radio-hover-bg: var(--oc-accent-a10) !important; --radio-hover-bg: var(--oc-accent-a10) !important;
--tab-hover-dark-bg: var(--oc-accent-a10) !important; --tab-hover-dark-bg: var(--oc-accent-a10) !important;
--scrollbar-thumb: var(--oc-neutral) !important; --scrollbar-thumb: var(--oc-scrollbar) !important;
--scrollbar-thumb-hover: var(--oc-text-muted) !important; --scrollbar-thumb-hover: var(--oc-accent-hover) !important;
border-radius: var(--radius-card); border-radius: var(--radius-card);
.developer-container, .developer-container,
@@ -91,6 +91,23 @@
} }
} }
// Editors inject important scrollbar colours. Override only control thumbs,
// retaining their existing widths, tracks and clipping.
textarea,
.oc,
.oc *,
.oc .cm-editor .cm-scroller,
.oc #debug-rendered,
.oc #debug-rendered pre {
&::-webkit-scrollbar-thumb {
background-color: var(--oc-scrollbar) !important;
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--oc-accent-hover) !important;
}
}
// Standard LuCI controls // Standard LuCI controls
.cbi-button.cbi-button-add, .cbi-button.cbi-button-add,
.cbi-button.cbi-button-apply, .cbi-button.cbi-button-apply,
@@ -287,6 +304,7 @@
} }
// Legacy help text is primary emphasis; success and error remain semantic. // Legacy help text is primary emphasis; success and error remain semantic.
b.oc-txt-good,
b[style*="color:green"], b[style*="color:green"],
b[style*="color: green"], b[style*="color: green"],
b[color="green"] { b[color="green"] {
@@ -329,6 +347,26 @@
} }
} }
// The version tab reuses the update popup inside an existing Argon card.
// Align its content with the tabs without changing standalone popups.
&[data-page="admin-services-openclash-settings"] {
[id="container.openclash.config.version_update"] {
padding-inline: var(--section-inset);
>.oc {
border-radius: 0;
overflow: visible;
}
#selectPopup.select-popup-tab {
border: 0;
border-radius: 0;
padding: 0;
background: transparent;
}
}
}
&[data-page="admin-services-openclash-config"], &[data-page="admin-services-openclash-config"],
&[data-page="admin-services-openclash-config-subscribe"], &[data-page="admin-services-openclash-config-subscribe"],
&[data-page^="admin-services-openclash-custom-dns-edit"], &[data-page^="admin-services-openclash-custom-dns-edit"],
@@ -492,6 +530,45 @@
padding: 0 1rem 1rem 1rem; padding: 0 1rem 1rem 1rem;
} }
// The surrounding form already provides the log card and horizontal inset.
// Retain the editor frame and clipping used to hide inactive log panes.
&[data-page="admin-services-openclash-log"] {
#tab.oc {
height: auto;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
>#tab-header {
padding: var(--field-gap) 0;
border: 0;
background: transparent;
>ul.oc-tabmenu {
background: transparent;
}
}
+.oc {
border-radius: 0;
}
}
#tab-content>.dom {
padding: 0;
}
.oc .select-popup-footer.log-footer {
margin: 0;
padding: var(--field-gap) 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
}
#cbi-openclash-dns_servers .cbi-section-table-row> #cbi-openclash-dns_servers .cbi-section-table-row>
.cbi-section-table-cell:last-child { .cbi-section-table-cell:last-child {
min-width: 7.25rem; min-width: 7.25rem;
@@ -3,6 +3,10 @@
// Native package-manager controls inject their own flex rules after the theme. // Native package-manager controls inject their own flex rules after the theme.
[data-page="admin-system-package-manager"], [data-page="admin-system-package-manager"],
[data-page="admin-system-opkg"] { [data-page="admin-system-opkg"] {
#view > #packages {
.native-table-card();
}
.controls { .controls {
min-width: 0; min-width: 0;
width: 100%; width: 100%;
+5
View File
@@ -12,6 +12,11 @@
left: 1rem; left: 1rem;
} }
// The view's section gap already separates the standalone page action.
#view > hr {
display: none;
}
.cbi-button { .cbi-button {
background: var(--warning-action-bg) !important; background: var(--warning-action-bg) !important;
border-color: var(--warning-action-bg) !important; border-color: var(--warning-action-bg) !important;
@@ -1,6 +1,10 @@
// out: false // out: false
[data-page="admin-status-processes"] { [data-page="admin-status-processes"] {
#view > .table {
.native-table-card();
}
.th:nth-child(3), .th:nth-child(3),
.td:nth-child(3) { .td:nth-child(3) {
text-align: start; text-align: start;
+23
View File
@@ -1,6 +1,29 @@
// out: false // out: false
[data-page="admin-system-admin-repokeys"] { [data-page="admin-system-admin-repokeys"] {
.cbi-section-table {
border-collapse: separate;
border-spacing: 0;
border-radius: inherit;
// The native heading has two cells but data rows also have an action
// column. Paint the row itself so the whole card has one continuous top.
> .thead,
> .thead > .tr {
border-start-start-radius: inherit;
border-start-end-radius: inherit;
background: var(--surface-muted);
}
> .thead > .tr > .th:first-child {
border-start-start-radius: inherit;
}
.cbi-section-table-row {
margin-block: 0;
}
}
textarea { textarea {
width: 100%; width: 100%;
min-width: 0; min-width: 0;
+11 -2
View File
@@ -1,10 +1,19 @@
// out: false // out: false
[data-page="admin-system-admin-sshkeys"] { [data-page="admin-system-admin-sshkeys"] {
#view > div:has(> .cbi-section-node > .cbi-dynlist) {
// This view omits cbi-map/cbi-section. Only the key list is a section;
// leave its page description between the heading and that card.
> .cbi-section-node {
.native-page-card();
padding: var(--field-gap) var(--section-inset);
}
}
.cbi-dynlist { .cbi-dynlist {
width: 100%; width: 100%;
margin-inline: var(--section-inset); margin: 0;
max-width: ~"min(48rem, calc(100% - 2 * var(--section-inset)))"; max-width: ~"min(48rem, 100%)";
gap: var(--field-gap); gap: var(--field-gap);
> .item { > .item {
+34
View File
@@ -0,0 +1,34 @@
// out: false
// Wake on LAN uses custom content outside the standard form field layout.
[data-page="admin-services-wol"] {
#cbi-luci-wol-defaults-_info {
padding-bottom: var(--field-gap);
line-height: 1.5;
> output {
width: 100%;
min-width: 0;
}
output p {
padding: 0;
}
.control-group {
margin-block-start: var(--control-gap);
}
}
.cbi-section:has(> #wol-output-text) {
padding-bottom: var(--field-gap);
}
#wol-output-text {
display: block;
// Override the application's inline width while preserving card insets.
width: calc(100% - var(--section-inset) - var(--section-inset)) !important;
margin-inline: var(--section-inset);
resize: vertical;
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ PKG_NAME:=qt6base
PKG_BASE:=6.12 PKG_BASE:=6.12
PKG_BUGFIX:=0 PKG_BUGFIX:=0
PKG_VERSION:=$(PKG_BASE).$(PKG_BUGFIX) PKG_VERSION:=$(PKG_BASE).$(PKG_BUGFIX)
PKG_RELEASE:=3 PKG_RELEASE:=4
PKG_SOURCE:=qtbase-everywhere-src-$(PKG_VERSION).tar.xz PKG_SOURCE:=qtbase-everywhere-src-$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=https://mirrors.tencent.com/qt/archive/qt/$(PKG_BASE)/$(PKG_VERSION)/submodules/ \ PKG_SOURCE_URL:=https://mirrors.tencent.com/qt/archive/qt/$(PKG_BASE)/$(PKG_VERSION)/submodules/ \
@@ -0,0 +1,13 @@
--- a/src/printsupport/dialogs/qprintdialog_unix.cpp
+++ b/src/printsupport/dialogs/qprintdialog_unix.cpp
@@ -486,8 +486,10 @@ bool QPrintPropertiesDialog::createAdvan
PDPK_PpdCustomOption, params);
if (hasCustomOption) {
auto *customValueEdit = new QLineEdit;
+#if QT_CONFIG(accessibility)
customValueEdit->setAccessibleName(
tr("Custom value for %1").arg(optionText));
+#endif
choicesCbWithLabelLayout->addWidget(customValueEdit);
choicesCb->setProperty(customValueLineEditProperty,
QVariant::fromValue(customValueEdit));