diff --git a/dns2tcp/Makefile b/dns2tcp/Makefile index e241421b9..5dae68331 100644 --- a/dns2tcp/Makefile +++ b/dns2tcp/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=dns2tcp PKG_VERSION:=1.1.2 -PKG_RELEASE:=3 +PKG_RELEASE:=4 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/zfl9/dns2tcp/tar.gz/v$(PKG_VERSION)? diff --git a/dns2tcp/patches/0001-add-A-option-to-filter-AAAA-answers.patch b/dns2tcp/patches/0001-add-A-option-to-filter-AAAA-answers.patch new file mode 100644 index 000000000..a11ba1003 --- /dev/null +++ b/dns2tcp/patches/0001-add-A-option-to-filter-AAAA-answers.patch @@ -0,0 +1,210 @@ +diff --git a/dns2tcp.c b/dns2tcp.c +index b289598..542f839 100644 +--- a/dns2tcp.c ++++ b/dns2tcp.c +@@ -35,6 +35,10 @@ + #define PORTSTRLEN 6 /* "65535" (include \0) */ + + #define DNS_MSGSZ 1472 /* mtu:1500 - iphdr:20 - udphdr:8 */ ++#define DNS_HDRLEN 12 ++ ++#define DNS_TYPE_A 1 ++#define DNS_TYPE_AAAA 28 + + /* ======================== helper ======================== */ + +@@ -128,6 +132,14 @@ typedef struct { + union skaddr srcaddr; + } ctx_t; + ++typedef struct { ++ size_t start; ++ size_t end; ++ uint16_t type; ++ uint8_t section; ++ bool drop; ++} dns_rr_t; ++ + /* ======================== global-vars ======================== */ + + enum { +@@ -135,6 +147,7 @@ enum { + FLAG_REUSE_PORT = 1 << 1, /* udp listen */ + FLAG_VERBOSE = 1 << 2, /* logging */ + FLAG_LOCAL_ADDR = 1 << 3, /* tcp local addr */ ++ FLAG_FILTER_AAAA = 1 << 4, /* strip all AAAA records from dns response */ + }; + + #define has_flag(flag) (g_flags & (flag)) +@@ -166,6 +179,126 @@ static void tcp_connect_cb(evloop_t *evloop, evio_t *watcher, int events); + static void tcp_sendmsg_cb(evloop_t *evloop, evio_t *watcher, int events); + static void tcp_recvmsg_cb(evloop_t *evloop, evio_t *watcher, int events); + ++static uint16_t read_be16(const void *ptr) { ++ const uint8_t *bytes = ptr; ++ return ((uint16_t)bytes[0] << 8) | bytes[1]; ++} ++ ++static void write_be16(void *ptr, uint16_t value) { ++ uint8_t *bytes = ptr; ++ bytes[0] = value >> 8; ++ bytes[1] = value & 0xff; ++} ++ ++static bool dns_skip_name(const uint8_t *msg, size_t msglen, size_t offset, size_t *next_offset) { ++ while (offset < msglen) { ++ uint8_t len = msg[offset]; ++ if (len == 0) { ++ *next_offset = offset + 1; ++ return true; ++ } ++ if ((len & 0xc0) == 0xc0) { ++ if (offset + 1 >= msglen) ++ return false; ++ *next_offset = offset + 2; ++ return true; ++ } ++ if ((len & 0xc0) || offset + 1 + len > msglen) ++ return false; ++ offset += 1 + len; ++ } ++ ++ return false; ++} ++ ++static size_t filter_aaaa_records(uint8_t *msg, size_t msglen) { ++ if (!has_flag(FLAG_FILTER_AAAA) || msglen < DNS_HDRLEN) ++ return msglen; ++ ++ uint16_t qdcount = read_be16(msg + 4); ++ uint16_t counts[] = { ++ read_be16(msg + 6), ++ read_be16(msg + 8), ++ read_be16(msg + 10), ++ }; ++ uint32_t rrcount = counts[0] + counts[1] + counts[2]; ++ ++ if (rrcount == 0 || qdcount > msglen / 5 || rrcount > msglen / 11) ++ return msglen; ++ ++ size_t offset = DNS_HDRLEN; ++ for (uint16_t i = 0; i < qdcount; i++) { ++ if (!dns_skip_name(msg, msglen, offset, &offset) || offset + 4 > msglen) ++ return msglen; ++ offset += 4; ++ } ++ ++ size_t rr_start = offset; ++ dns_rr_t *rrs = calloc(rrcount, sizeof(*rrs)); ++ if (!rrs) ++ return msglen; ++ ++ uint32_t rridx = 0; ++ for (uint8_t section = 0; section < 3; section++) { ++ for (uint16_t i = 0; i < counts[section]; i++) { ++ dns_rr_t *rr = &rrs[rridx++]; ++ rr->start = offset; ++ rr->section = section; ++ ++ if (!dns_skip_name(msg, msglen, offset, &offset) || offset + 10 > msglen) ++ goto out; ++ ++ rr->type = read_be16(msg + offset); ++ uint16_t rdlen = read_be16(msg + offset + 8); ++ rr->end = offset + 10 + rdlen; ++ if (rr->end > msglen) ++ goto out; ++ ++ offset = rr->end; ++ } ++ } ++ ++ size_t rest_start = offset; ++ bool changed = false; ++ uint16_t keep_counts[] = { counts[0], counts[1], counts[2] }; ++ ++ for (uint32_t i = 0; i < rrcount; i++) { ++ if (rrs[i].type == DNS_TYPE_AAAA) { ++ rrs[i].drop = true; ++ keep_counts[rrs[i].section]--; ++ changed = true; ++ } ++ } ++ ++ if (changed) { ++ size_t write_offset = rr_start; ++ ++ for (uint32_t i = 0; i < rrcount; i++) { ++ if (rrs[i].drop) ++ continue; ++ ++ size_t rrlen = rrs[i].end - rrs[i].start; ++ if (write_offset != rrs[i].start) ++ memmove(msg + write_offset, msg + rrs[i].start, rrlen); ++ write_offset += rrlen; ++ } ++ ++ if (write_offset != rest_start) ++ memmove(msg + write_offset, msg + rest_start, msglen - rest_start); ++ ++ msglen = write_offset + (msglen - rest_start); ++ write_be16(msg + 6, keep_counts[0]); ++ write_be16(msg + 8, keep_counts[1]); ++ write_be16(msg + 10, keep_counts[2]); ++ log_verbose("filter AAAA records, answer:%hu -> %hu, authority:%hu -> %hu, additional:%hu -> %hu", ++ counts[0], keep_counts[0], counts[1], keep_counts[1], counts[2], keep_counts[2]); ++ } ++ ++out: ++ free(rrs); ++ return msglen; ++} ++ + static void print_help(void) { + printf("usage: dns2tcp <-L listen> <-R remote> [options...]\n" + " -L udp listen address, port default to 53\n" +@@ -174,6 +307,7 @@ static void print_help(void) { + " -s set TCP_SYNCNT option for tcp socket\n" + " -6 set IPV6_V6ONLY option for udp socket\n" + " -r set SO_REUSEPORT option for udp socket\n" ++ " -A strip all AAAA records from dns response\n" + " -v print verbose log, used for debugging\n" + " -V print version number of dns2tcp and exit\n" + " -h print help information of dns2tcp and exit\n" +@@ -257,7 +391,7 @@ static void parse_opt(int argc, char *argv[]) { + + opterr = 0; + int shortopt; +- const char *optstr = "L:R:l:s:6rafvVh"; ++ const char *optstr = "L:R:l:s:6rAafvVh"; + while ((shortopt = getopt(argc, argv, optstr)) != -1) { + switch (shortopt) { + case 'L': +@@ -295,6 +429,9 @@ static void parse_opt(int argc, char *argv[]) { + case 'r': + add_flag(FLAG_REUSE_PORT); + break; ++ case 'A': ++ add_flag(FLAG_FILTER_AAAA); ++ break; + case 'a': + /* nop */ + break; +@@ -399,6 +536,7 @@ int main(int argc, char *argv[]) { + if (g_syn_cnt) log_info("enable TCP_SYNCNT:%hhu sockopt", g_syn_cnt); + if (has_flag(FLAG_IPV6_V6ONLY)) log_info("enable IPV6_V6ONLY sockopt"); + if (has_flag(FLAG_REUSE_PORT)) log_info("enable SO_REUSEPORT sockopt"); ++ if (has_flag(FLAG_FILTER_AAAA)) log_info("enable AAAA record filter"); + log_verbose("print the verbose log"); + + g_listen_fd = create_socket(skaddr_family(&g_listen_skaddr), SOCK_DGRAM); +@@ -532,6 +670,9 @@ static void tcp_recvmsg_cb(evloop_t *evloop, evio_t *watcher, int events __unuse + uint16_t msglen; + if (ctx->nbytes < 2 || ctx->nbytes < 2 + (msglen = ntohs(*(uint16_t *)buffer))) return; + ++ msglen = filter_aaaa_records((uint8_t *)buffer + 2, msglen); ++ *(uint16_t *)buffer = htons(msglen); ++ + ssize_t nsend = sendto(g_listen_fd, buffer + 2, msglen, 0, &ctx->srcaddr.sa, skaddr_len(&ctx->srcaddr)); + if (nsend < 0 || verbose) { + char ip[IP6STRLEN]; diff --git a/luci-app-ssr-plus/Makefile b/luci-app-ssr-plus/Makefile index df0d6eba0..3565ff6da 100644 --- a/luci-app-ssr-plus/Makefile +++ b/luci-app-ssr-plus/Makefile @@ -3,77 +3,51 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=luci-app-ssr-plus LUCI_PKGARCH:=all PKG_NAME:=luci-app-ssr-plus -PKG_VERSION:=190 -PKG_RELEASE:=18 +PKG_VERSION:=193 +PKG_RELEASE:=19 PKG_CONFIG_DEPENDS:= \ CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \ CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS_RUST \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNS2TCP \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Tuic_Client \ + CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Http_Proxy \ + CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Mihomo \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2 \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Client \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan + CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server -LUCI_TITLE:=SS/SSR/V2Ray/Trojan/NaiveProxy/Tuic/ShadowTLS/Hysteria/Socks5/Tun LuCI interface +LUCI_TITLE:=SS/SSR/V2Ray/Trojan/NaiveProxy/Tuic/ShadowTLS/Hysteria/Socks5/Clash LuCI interface LUCI_PKGARCH:=all LUCI_DEPENDS:= \ - +coreutils +coreutils-base64 +dns2tcp +dnsmasq-full \ - +jq +ip-full +lua +lua-neturl +libuci-lua +microsocks \ - +tcping +resolveip +shadowsocksr-libev-ssr-check +curl +nping \ - +PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:curl \ - +PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Xray:curl \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Xray:xray-core \ + +libuci-lua +lua +luci-compat +coreutils +coreutils-base64 +dns2tcp +dnsmasq-full \ + +jq +ip-full +lua-neturl +libuci-lua +microsocks +ipt2socks +lyaml \ + +resolveip +curl +nping +unzip +xz-utils \ +PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG:chinadns-ng \ - +PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS:dns2socks \ - +PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS_RUST:dns2socks-rust \ - +PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY:dnsproxy \ +PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS:mosdns \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria:hysteria \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Tuic_Client:tuic-client \ +PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS:shadow-tls \ - +PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks:ipt2socks \ +PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun:kcptun-client \ +PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy:naiveproxy \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2:redsocks2 \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-local \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-redir \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server:shadowsocks-libev-ss-server \ +PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client:shadowsocks-rust-sslocal \ +PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server:shadowsocks-rust-ssserver \ +PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs:simple-obfs-client \ +PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin:v2ray-plugin \ +PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-local \ +PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \ - +PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server \ - +PACKAGE_$(PKG_NAME)_INCLUDE_Trojan:trojan + +PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server define Package/$(PKG_NAME)/config -select PACKAGE_luci-lua-runtime if PACKAGE_$(PKG_NAME) choice prompt "Transparent Proxy Backend" @@ -103,37 +77,24 @@ endchoice choice prompt "Shadowsocks Client Selection" - default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client if aarch64 || x86_64 - default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client + default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Client config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Client bool "None" - config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client - bool "Shadowsocks-libev" - config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client - bool "Shadowsocks-rust" - depends on aarch64||arm||i386||mips||mipsel||x86_64 - depends on !(TARGET_x86_geode||TARGET_x86_legacy) + bool "Shadowsocks Rust" endchoice choice prompt "Shadowsocks Server Selection" - default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server if aarch64 - default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server if i386||x86_64||arm default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server bool "None" - config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server - bool "Shadowsocks-libev" - config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server - bool "Shadowsocks-rust" - depends on aarch64||arm||i386||mips||mipsel||x86_64 - depends on !(TARGET_x86_geode||TARGET_x86_legacy) + bool "Shadowsocks Rust" endchoice choice @@ -144,45 +105,35 @@ choice config PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY bool "None" - config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray - bool "V2ray-core" - config PACKAGE_$(PKG_NAME)_INCLUDE_Xray bool "Xray-core" + select PACKAGE_xray-core endchoice config PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG bool "Include ChinaDNS-NG" - default n - -config PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS - bool "Include DNS2socks" - default y - -config PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS_RUST - bool "Include DNS2socks-Rust" - default n - -config PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY - bool "Include DNSproxy" - default n + default y if aarch64||arm||i386||x86_64 config PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS bool "Include MosDNS" - default y if aarch64||i386||x86_64 - -config PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria - bool "Include Hysteria" - select PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG default n -config PACKAGE_$(PKG_NAME)_INCLUDE_Tuic_Client - bool "Include Tuic-Client" - select PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG - select PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks - depends on aarch64||arm||i386||x86_64 - depends on !(TARGET_x86_geode||TARGET_x86_legacy) - default n +config PACKAGE_$(PKG_NAME)_INCLUDE_Http_Proxy + bool "Include HTTP(S) Proxy Server" + select PACKAGE_3proxy + default y if (aarch64||arm||i386||loongarch64||riscv64||x86_64) + +config PACKAGE_$(PKG_NAME)_INCLUDE_Mihomo + bool "Include Mihomo (Clash Support)" + select PACKAGE_mihomo + depends on aarch64||arm||i386||loongarch64||riscv64||x86_64 + default y if aarch64||arm||i386||loongarch64||riscv64||x86_64 + +config PACKAGE_$(PKG_NAME)_INCLUDE_GeoData + bool "Include GeoData (GeoIP and GeoSite)" + select PACKAGE_v2ray-geoip + select PACKAGE_v2ray-geosite + default y if i386||x86_64 config PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS bool "Include Shadow-TLS" @@ -192,10 +143,6 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS depends on !(TARGET_x86_geode||TARGET_x86_legacy) default n -config PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks - bool "Include IPT2Socks" - default n - config PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun bool "Include Kcptun" default n @@ -205,13 +152,9 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy depends on !(arc||armeb||mips||mips64||powerpc||TARGET_gemini) default n -config PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2 - bool "Include Redsocks2" - default n - config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs bool "Include Shadowsocks Simple Obfs Plugin" - default y + default n config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin bool "Include Shadowsocks V2ray Plugin" @@ -223,11 +166,6 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server bool "Include ShadowsocksR Libev Server" - default y if i386||x86_64||arm - -config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan - bool "Include Trojan" - select PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks default n endef @@ -240,4 +178,41 @@ endef include $(TOPDIR)/feeds/luci/luci.mk +define Package/$(PKG_NAME)/install + ifneq ($(wildcard ${CURDIR}/luasrc),) + $(INSTALL_DIR) $(1)$(LUCI_LIBRARYDIR) + cp -pR $(PKG_BUILD_DIR)/luasrc/* $(1)$(LUCI_LIBRARYDIR)/ + $(FIND) $(1)$(LUCI_LIBRARYDIR)/ -type f -name '*.luadoc' | $(XARGS) rm + $(if $(CONFIG_LUCI_SRCDIET),$(call SrcDiet,$(1)$(LUCI_LIBRARYDIR)/),true) + $(call SubstituteVersion,$(1)$(LUCI_LIBRARYDIR)/) + endif + ifneq ($(wildcard ${CURDIR}/ucode),) + $(INSTALL_DIR) $(1)$(UCODE_LIBRARYDIR) + cp -pR $(PKG_BUILD_DIR)/ucode/* $(1)$(UCODE_LIBRARYDIR)/ + $(call SubstituteVersion,$(1)$(UCODE_LIBRARYDIR)/) + endif + ifneq ($(wildcard ${CURDIR}/htdocs),) + $(INSTALL_DIR) $(1)$(HTDOCS) + cp -pR $(PKG_BUILD_DIR)/htdocs/* $(1)$(HTDOCS)/ + $(if $(CONFIG_LUCI_JSMIN),$(call JsMin,$(1)$(HTDOCS)/),true) + $(if $(CONFIG_LUCI_CSSTIDY),$(call CssTidy,$(1)$(HTDOCS)/),true) + endif + ifneq ($(wildcard ${CURDIR}/root),) + $(INSTALL_DIR) $(1)/ + cp -pR $(PKG_BUILD_DIR)/root/* $(1)/ + endif + ifneq ($(wildcard ${CURDIR}/src),) + $(call Build/Install/Default) + $(CP) $(PKG_INSTALL_DIR)/* $(1)/ + endif + if [ -d "$(1)/usr/bin" ]; then \ + chmod 0755 "$(1)/usr/bin/ssr-monitor" 2>/dev/null || true; \ + chmod 0755 "$(1)/usr/bin/ssr-rules" 2>/dev/null || true; \ + chmod 0755 "$(1)/usr/bin/ssr-switch" 2>/dev/null || true; \ + fi + if [ -f "$(1)/etc/hotplug.d/iface/99-ssrplus-pppoe" ]; then \ + chmod 0755 "$(1)/etc/hotplug.d/iface/99-ssrplus-pppoe" 2>/dev/null || true; \ + fi +endef + # call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-ssr-plus/htdocs/luci-static/resources/view/shadowsocksr/Sortable.min.js b/luci-app-ssr-plus/htdocs/luci-static/resources/view/shadowsocksr/Sortable.min.js deleted file mode 100644 index 8148b9dda..000000000 --- a/luci-app-ssr-plus/htdocs/luci-static/resources/view/shadowsocksr/Sortable.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! Sortable 1.15.7 - MIT | git://github.com/SortableJS/Sortable.git */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function o(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,o=Array(e);n"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function m(t){return t.host&&t!==document&&t.host.nodeType&&t.host!==t?t.host:t.parentNode}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&g(t,e)||o&&t===n)return t}while(t!==n&&(t=m(t)))}return null}var v,b=/\s+/g;function k(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(b," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(b," ")))}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function D(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function E(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[K]._onDragOver(o)}}var i,r,a}function jt(t){$&&$.parentNode[K]._isOutsideThisEl(t.target)}function Ht(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[K]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Rt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Ht.supportPointer&&"PointerEvent"in window&&(!u||d),emptyInsertThreshold:5};for(n in G.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in Xt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&Pt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?f(t,"pointerdown",this._onTapStart):(f(t,"mousedown",this._onTapStart),f(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(f(t,"dragover",this),f(t,"dragenter",this)),_t.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,N())}function Lt(t,e,n,o,i,r,a,l){var s,c,u=t[K],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function Kt(t){t.draggable=!1}function Wt(){Ot=!1}function zt(t){return setTimeout(t,0)}function Gt(t){return clearTimeout(t)}Ht.prototype={constructor:Ht,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(bt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,$):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){Mt.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&Mt.push(o)}}(o),!$&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=P(l,t.draggable,o,!1))&&l.animated||nt===l)){if(rt=j(l),lt=j(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return Z({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),q("filter",n,{evt:e}),void(i&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return Z({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),q("filter",n,{evt:e}),!0}))return void(i&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!$&&n.parentNode===r&&(o=X(n),tt=r,Q=($=n).parentNode,et=$.nextSibling,nt=n,ct=a.group,dt={target:Ht.dragged=$,clientX:(e||t).clientX,clientY:(e||t).clientY},gt=dt.clientX-o.left,mt=dt.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,$.style["will-change"]="all",o=function(){q("delayEnded",i,{evt:t}),Ht.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!c&&i.nativeDraggable&&($.draggable=!0),i._triggerDragStart(t,e),Z({sortable:i,name:"choose",originalEvent:t}),k($,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){E($,t.trim(),Kt)}),f(l,"dragover",Ft),f(l,"mousemove",Ft),f(l,"touchmove",Ft),a.supportPointer?(f(l,"pointerup",i._onDrop),this.nativeDraggable||f(l,"pointercancel",i._onDrop)):(f(l,"mouseup",i._onDrop),f(l,"touchend",i._onDrop),f(l,"touchcancel",i._onDrop)),c&&this.nativeDraggable&&(this.options.touchStartThreshold=4,$.draggable=!0),q("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():Ht.eventCanceled?this._onDrop():(a.supportPointer?(f(l,"pointerup",i._disableDelayedDrag),f(l,"pointercancel",i._disableDelayedDrag)):(f(l,"mouseup",i._disableDelayedDrag),f(l,"touchend",i._disableDelayedDrag),f(l,"touchcancel",i._disableDelayedDrag)),f(l,"mousemove",i._delayedDragTouchMoveHandler),f(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&f(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){$&&Kt($),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;p(t,"mouseup",this._disableDelayedDrag),p(t,"touchend",this._disableDelayedDrag),p(t,"touchcancel",this._disableDelayedDrag),p(t,"pointerup",this._disableDelayedDrag),p(t,"pointercancel",this._disableDelayedDrag),p(t,"mousemove",this._delayedDragTouchMoveHandler),p(t,"touchmove",this._delayedDragTouchMoveHandler),p(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?f(document,"pointermove",this._onTouchMove):f(document,e?"touchmove":"mousemove",this._onTouchMove):(f($,"dragend",this),f(tt,"dragstart",this._onDragStart));try{document.selection?zt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;Et=!1,tt&&$?(q("dragStarted",this,{evt:e}),this.nativeDraggable&&f(document,"dragover",jt),n=this.options,t||k($,n.dragClass,!1),k($,n.ghostClass,!0),Ht.active=this,t&&this._appendGhost(),Z({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(ht){this._lastX=ht.clientX,this._lastY=ht.clientY,Yt();for(var t=document.elementFromPoint(ht.clientX,ht.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(ht.clientX,ht.clientY))!==e;)e=t;if($.parentNode[K]._isOutsideThisEl(t),e)do{if(e[K])if(e[K]._onDragOver({clientX:ht.clientX,clientY:ht.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=m(t=e));Bt()}},_onTouchMove:function(t){if(dt){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=J&&D(J,!0),a=J&&r&&r.a,l=J&&r&&r.d,e=Nt&&Dt&&S(Dt),a=(i.clientX-dt.clientX+o.x)/(a||1)+(e?e[0]-xt[0]:0)/(a||1),l=(i.clientY-dt.clientY+o.y)/(l||1)+(e?e[1]-xt[1]:0)/(l||1);if(!Ht.active&&!Et){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))E.right+10||S.clientY>x.bottom&&S.clientX>x.left:S.clientY>E.bottom+10||S.clientX>x.right&&S.clientY>x.top)||m.animated)){if(m&&(t=n,e=r,C=X(B((_=this).el,0,_.options,!0)),_=L(_.el,_.options,J),e?t.clientX<_.left-10||t.clientY/dev/null 2>&1") == 0 +local function parse_nonnegative_int(value) + local number = tonumber(value) + if not number then + return nil + end + number = math.floor(number) + if number < 0 then + return nil + end + return number end -local function uci_save(cursor, config, commit, apply) - if is_old_uci() then - cursor:save(config) - if commit then - cursor:commit(config) - if apply then - luci.sys.call("/etc/init.d/" .. config .. " reload > /dev/null 2>&1 &") - end - end - else - commit = true - if commit then - if apply then - cursor:commit(config) +local function sanitize_mac(value) + value = trim(value):upper():gsub("-", ":") + if value:match("^%x%x:%x%x:%x%x:%x%x:%x%x:%x%x$") then + return value + end + return "" +end + +local function normalize_client_ip(value) + value = trim(value) + if value == "" then + return "" + end + if datatypes.cidr4(value) or datatypes.ip4addr(value) then + return value + end + return "" +end + +local function get_clash_client_rule_csv_path(sid) + sid = trim(sid) + if sid == "" then + return nil + end + return string.format("%s/%s.csv", CLASH_RULES_DIR, sid) +end + +local function csv_escape(value) + value = tostring(value or "") + if value:find('[",\n\r]') then + return '"' .. value:gsub('"', '""') .. '"' + end + return value +end + +local function parse_csv_line(line) + local cols = {} + local cur = "" + local in_quote = false + local i = 1 + + while i <= #line do + local ch = line:sub(i, i) + if ch == '"' then + if in_quote and line:sub(i + 1, i + 1) == '"' then + cur = cur .. '"' + i = i + 1 else - sh_uci_commit(config) + in_quote = not in_quote + end + elseif ch == "," and not in_quote then + cols[#cols + 1] = cur + cur = "" + else + cur = cur .. ch + end + i = i + 1 + end + + cols[#cols + 1] = cur + return cols +end + +local function read_clash_client_rules_csv(sid) + local rules = {} + local csv_path = get_clash_client_rule_csv_path(sid) + if not csv_path or not nixio.fs.access(csv_path) then + return rules + end + + local raw = nixio.fs.readfile(csv_path) + if not raw or raw == "" then + return rules + end + + local first = true + for line in tostring(raw):gsub("\r", ""):gmatch("[^\n]+") do + local text = trim(line) + if text ~= "" then + if first and text:lower() == "enabled,client,policy,remarks,client_mac" then + first = false + else + local cols = parse_csv_line(line) + if #cols >= 4 then + rules[#rules + 1] = { + id = tostring(#rules + 1), + enabled = cols[1] == "1" or tostring(cols[1] or ""):lower() == "true", + ip_addr = trim(cols[2] or ""), + policy_group = trim(cols[3] or ""), + remarks = trim(cols[4] or ""), + client_mac = sanitize_mac(cols[5] or "") + } + end end end end + + return rules end -local function url(...) - local url = string.format("admin/services/%s", "shadowsocksr") - local args = { ... } - for i, v in ipairs(args) do - if v and v ~= "" then - url = url .. "/" .. v +local function write_clash_client_rules_csv(sid, rows) + local csv_path = get_clash_client_rule_csv_path(sid) + if not csv_path then + return false + end + + nixio.fs.mkdirr(CLASH_RULES_DIR) + local lines = { "enabled,client,policy,remarks,client_mac" } + for _, row in ipairs(rows or {}) do + lines[#lines + 1] = table.concat({ + row.enabled == "1" and "1" or "0", + csv_escape(row.ip_addr or ""), + csv_escape(row.policy_group or ""), + csv_escape(row.remarks or ""), + csv_escape(row.client_mac or "") + }, ",") + end + + return nixio.fs.writefile(csv_path, table.concat(lines, "\n") .. "\n") +end + +local function collect_lan_clients() + local clients = {} + local seen = {} + + luci.sys.net.host_hints(function(mac, ipv4, _, name) + local ip = trim(ipv4) + local norm_mac = sanitize_mac(mac) + if ip ~= "" and not seen[ip] then + seen[ip] = true + clients[#clients + 1] = { + ip = ip, + mac = norm_mac, + name = trim(name) ~= "" and trim(name) or ip + } + end + end) + + table.sort(clients, function(a, b) + return tostring(a.name or a.ip) < tostring(b.name or b.ip) + end) + + return clients +end + +local function read_clash_client_rules(sid) + return read_clash_client_rules_csv(sid) +end + +local function normalize_ping_ms(value, scale) + local num = tonumber(value) + if not num or num <= 0 then + return nil + end + local scaled = scale and (num * scale) or num + if scaled > 0 and scaled < 1 then + return 1 + end + return math.floor(scaled + 0.5) +end + +local function detect_tls_handshake_ms(domain, port, path, resolve_host, server_ip, is_websocket) + if not domain or domain == "" or not port or port <= 0 then + return nil + end + + local final_host = (resolve_host and resolve_host ~= "") and resolve_host or domain + local resolve_arg = "" + if server_ip and server_ip ~= "" and final_host ~= server_ip then + resolve_arg = string.format("--resolve '%s:%d:%s' ", final_host, port, server_ip) + end + + local ws_headers = "" + if is_websocket then + ws_headers = string.format( + "-H %s -H %s -H %s -H %s ", + luci.util.shellquote("Connection: Upgrade"), + luci.util.shellquote("Upgrade: websocket"), + luci.util.shellquote("Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ=="), + luci.util.shellquote("Sec-WebSocket-Version: 13") + ) + end + + local host_header = (final_host and final_host ~= "") and ("-H " .. luci.util.shellquote("Host: " .. final_host) .. " ") or "" + local url = string.format("https://%s:%d%s", final_host, port, path or "") + local cmd = string.format( + "curl --http1.1 -m 3 -ksS -o /dev/null %s%s%s -w 'time_connect=%%{time_connect}\\ntime_appconnect=%%{time_appconnect}\\nhttp_code=%%{http_code}' '%s' 2>/dev/null", + resolve_arg, host_header, ws_headers, url + ) + local result = luci.sys.exec(cmd) or "" + local appconnect = tonumber(result:match("time_appconnect=([0-9.]+)")) + if appconnect and appconnect > 0 then + return normalize_ping_ms(appconnect, 1000) + end + local connect = tonumber(result:match("time_connect=([0-9.]+)")) + if connect and connect > 0 then + return normalize_ping_ms(connect, 1000) + end + return nil +end + +local function urlencode(str) + if not str then return "" end + return tostring(str):gsub("[^%w%-_%.~]", function(c) + return string.format("%%%02X", string.byte(c)) + end) +end + +local function is_ipv6_address(addr) + addr = tostring(addr or "") + return addr ~= "" and addr:find(":", 1, true) ~= nil +end + +local function is_local_target(addr) + addr = tostring(addr or ""):lower() + if addr == "" then + return false + end + + if addr == "localhost" or addr == "::1" or addr:match("%.local$") then + return true + end + + if is_ipv6_address(addr) then + return addr:match("^fe[89ab]") ~= nil or addr:match("^fc") ~= nil or addr:match("^fd") ~= nil + end + + local o1, o2 = addr:match("^(%d+)%.(%d+)%.") + o1 = tonumber(o1) + o2 = tonumber(o2) + if not o1 or not o2 then + return false + end + + return o1 == 10 + or o1 == 127 + or (o1 == 169 and o2 == 254) + or (o1 == 172 and o2 >= 16 and o2 <= 31) + or (o1 == 192 and o2 == 168) +end + +local function detect_tcp_connect_ms(domain, port) + if not domain or domain == "" or not port or port <= 0 then + return nil + end + + local ip_version_arg = is_ipv6_address(domain) and "-6 " or "" + local cmd = string.format( + "nping %s--tcp-connect -q -c 1 -p %d %s 2>/dev/null", + ip_version_arg, + port, + luci.util.shellquote(domain) + ) + local result = luci.sys.exec(cmd) or "" + local success = tonumber(result:match("Successful connections:%s*([0-9]+)")) + if success and success > 0 then + local avg_rtt = tonumber(result:match("Avg rtt:%s*([0-9.]+)ms")) + if avg_rtt and avg_rtt > 0 and avg_rtt < 1 and not is_local_target(domain) then + return nil + end + return normalize_ping_ms(avg_rtt) + end + + return nil +end + +local function get_clash_secret(sid) + return sid .. "_ssrplus_clash" +end + +local function get_clash_cache_file(sid) + return "/etc/ssrplus/clash/" .. sid .. ".yaml" +end + +local function get_clash_state_file(sid) + return "/etc/ssrplus/clash/" .. sid .. ".cache.db" +end + +local function clash_process_running() + return luci.sys.call("(busybox ps -w 2>/dev/null || busybox ps) | grep ssr-retcp | grep -v grep >/dev/null") == 0 +end + +local function global_client_running() + local process_list = luci.sys.exec("busybox ps -w 2>/dev/null || busybox ps") + local global_server = uci:get_first("shadowsocksr", "global", "global_server", "nil") + local global_type = global_server ~= "nil" and (uci:get("shadowsocksr", global_server, "type") or "") or "" + + if process_list:find("tcp.only.ssr.retcp") + or process_list:find("tcp.udp.ssr.retcp") + or process_list:find("local.ssr.retcp") + or process_list:find("local.udp.ssr.retcp") then + return true + end + + if (global_type == "clash" or global_type == "tuic" or global_type == "ss") + and process_list:find("ssr%-retcp") then + return true + end + + if (global_type == "clash" or global_type == "tuic" or global_type == "ss") + and process_list:find("mihomo") + and (process_list:find("/clash%-") or process_list:find("/tuic%-") or process_list:find("/ss%-")) then + return true + end + + if global_type == "socks5" + and process_list:find("ipt2socks") + and (process_list:find("%-T") or process_list:find("%-%-tcp%-only")) then + return true + end + + return false +end + +local function get_active_node_runtime(sid) + if not sid or sid == "" or sid == "nil" or uci:get("shadowsocksr", sid) ~= "servers" then + return nil, nil + end + + local stype = (uci:get("shadowsocksr", sid, "type") or ""):lower() + local proto = (uci:get("shadowsocksr", sid, "v2ray_protocol") or ""):lower() + local backend + local protocol + + if stype == "ss" then + backend = translate("Mihomo") + protocol = translate("Shadowsocks") + elseif stype == "clash" then + backend = translate("Mihomo") + protocol = translate("Clash") + elseif stype == "tuic" then + backend = translate("Mihomo") + protocol = translate("TUIC") + elseif stype == "ssr" then + backend = translate("ShadowsocksR") + elseif stype == "ss-rust" then + backend = translate("Shadowsocks-rust") + elseif stype == "v2ray" then + local proto_map = { + vmess = "VMess", + vless = "VLESS", + trojan = "Trojan", + socks = "SOCKS5", + hysteria2 = "Hysteria2", + shadowsocks = "Shadowsocks", + http = "HTTP" + } + backend = translate("Xray") + if proto_map[proto] then + protocol = translate(proto_map[proto]) + end + elseif stype == "trojan" then + backend = translate("Trojan") + elseif stype == "naiveproxy" then + backend = translate("NaiveProxy") + elseif stype == "socks5" then + backend = translate("SOCKS5") + elseif stype == "shadowtls" then + backend = translate("ShadowTLS") + elseif stype == "hysteria2" then + backend = translate("Hysteria2") + end + + if not backend or backend == "" then + backend = trim(stype) + end + + return backend, protocol +end + +local function get_running_status_text() + local sid = uci:get_first("shadowsocksr", "global", "global_server", "nil") + local backend, protocol = get_active_node_runtime(sid) + + if backend and backend ~= "" and protocol and protocol ~= "" and backend ~= protocol then + return string.format(translate("RUNNING in %s (%s) Mode"), backend, protocol) + end + + if backend and backend ~= "" then + return string.format(translate("RUNNING in %s Mode"), backend) + end + + return translate("RUNNING") +end + +local function is_active_clash_node(sid) + if not sid then return false end + if uci:get("shadowsocksr", sid) ~= "servers" then return false end + if uci:get("shadowsocksr", sid, "type") ~= "clash" then return false end + if uci:get_first("shadowsocksr", "global", "global_server") ~= sid then return false end + return clash_process_running() +end + +local function resolve_active_clash_sid(sid) + if is_active_clash_node(sid) then + return sid + end + + local current_sid = uci:get_first("shadowsocksr", "global", "global_server") + if is_active_clash_node(current_sid) then + return current_sid + end + + return nil +end + +local function clash_api_request(sid, method, path, body) + sid = resolve_active_clash_sid(sid) + if not sid then + return nil + end + local secret = get_clash_secret(sid) + local cmd = string.format( + "curl -sL -m 5 --retry 1 -w '\n__CURL_STATUS__:%%{http_code}' -H %s -H %s -X %s http://127.0.0.1:%s%s %s", + luci.util.shellquote("Content-Type: application/json"), + luci.util.shellquote("Authorization: Bearer " .. secret), + method, + CLASH_API_PORT, + path, + body and ("-d " .. luci.util.shellquote(body)) or "" + ) + local output = luci.sys.exec(cmd) + if not output or output == "" then + return nil + end + local body_output = output:gsub("\n__CURL_STATUS__:%d%d%d%s*$", "") + local code = output:match("__CURL_STATUS__:(%d%d%d)") + return { + code = tonumber(code), + body = body_output + } +end + +local function use_fw4_backend() + return luci.sys.call("command -v fw4 >/dev/null") == 0 +end + +local function parse_clash_groups(raw) + local info = json.parse(raw or "") + local proxies = info and info.proxies or nil + local groups = {} + if type(proxies) ~= "table" then + return groups + end + for name, value in pairs(proxies) do + if type(value) == "table" and type(value.all) == "table" and #value.all > 0 then + groups[#groups + 1] = { + name = name, + type = value.type or "", + now = value.now or "", + all = value.all + } end end - return require "luci.dispatcher".build_url(url) + table.sort(groups, function(a, b) return tostring(a.name) < tostring(b.name) end) + return groups +end + +local function parse_kv_output(raw) + local data = {} + for line in tostring(raw or ""):gmatch("[^\r\n]+") do + local key, value = line:match("^([%w_]+)=(.*)$") + if key then + data[key] = value + end + end + return data +end + +local function with_detect_cache_lock(fn) + for _ = 1, 40 do + if nixio.fs.mkdir(SERVER_DETECT_LOCK) then + local ok, ret = pcall(fn) + nixio.fs.rmdir(SERVER_DETECT_LOCK) + if ok then + return ret + end + return nil + end + nixio.nanosleep(0, 50000000) + end + return nil +end + +local function load_detect_cache() + local raw = nixio.fs.readfile(SERVER_DETECT_CACHE) + if not raw or raw == "" then + return {} + end + local parsed = json.parse(raw) + return type(parsed) == "table" and parsed or {} +end + +local function save_detect_cache_entry(sid, data) + if not sid or sid == "" then + return + end + with_detect_cache_lock(function() + local cache = load_detect_cache() + cache[sid] = data + nixio.fs.writefile(SERVER_DETECT_CACHE, json.stringify(cache)) + end) +end + +local function read_component_state(component, action) + if not SUPPORTED_COMPONENTS[component] then + return nil, 400, "unsupported_component" + end + + local mirror = luci.http.formvalue("mirror") or "" + local cmd = string.format( + "COMPONENT_MIRROR=%s /bin/sh %s %s 2>/dev/null", + luci.util.shellquote(mirror), + luci.util.shellquote(COMPONENT_HELPER), + luci.util.shellquote(component .. "_" .. action) + ) + return parse_kv_output(luci.sys.exec(cmd)) +end + +local function read_geo_state(component, action) + if not SUPPORTED_GEO_COMPONENTS[component] then + return nil, 400, "unsupported_component" + end + + local mirror = luci.http.formvalue("mirror") or "" + local cmd = string.format( + "COMPONENT_MIRROR=%s /bin/sh %s %s 2>/dev/null", + luci.util.shellquote(mirror), + luci.util.shellquote(COMPONENT_HELPER), + luci.util.shellquote(component .. "_" .. action) + ) + return parse_kv_output(luci.sys.exec(cmd)) +end + +local function write_component_json(data) + luci.http.prepare_content("application/json") + luci.http.write_json({ + component = data.component or "xray", + installed = data.installed == "1", + current_version = data.current_version or "", + latest_version = data.latest_version or "", + previous_version = data.previous_version or "", + arch = data.arch or "", + asset = data.asset or "", + can_upgrade = data.can_upgrade == "1", + success = data.success == "1", + error = data.error or "", + message = data.message or "" + }) +end + +local function write_geo_json(data) + luci.http.prepare_content("application/json") + luci.http.write_json({ + component = data.component or "country_mmdb", + installed = data.installed == "1", + current_version = data.current_version or "", + current_version_extra = data.current_version_extra or "", + latest_version = data.latest_version or "", + can_upgrade = data.can_upgrade == "1", + success = data.success == "1", + error = data.error or "", + message = data.message or "" + }) +end + +local function shell_quote(value) + return "'" .. tostring(value or ""):gsub("'", "'\\''") .. "'" end function index() @@ -54,31 +623,47 @@ function index() page.dependent = true page.acl_depends = { "luci-app-ssr-plus" } entry({"admin", "services", "shadowsocksr", "client"}, cbi("shadowsocksr/client"), _("SSR Client"), 10).leaf = true - entry({"admin", "services", "shadowsocksr", "servers"}, arcombine(cbi("shadowsocksr/servers", {autoapply = true}), cbi("shadowsocksr/client-config")), _("Servers Nodes"), 20).leaf = true + entry({"admin", "services", "shadowsocksr", "servers"}, arcombine(cbi("shadowsocksr/servers"), cbi("shadowsocksr/client-config")), _("Servers Nodes"), 20).leaf = true entry({"admin", "services", "shadowsocksr", "control"}, cbi("shadowsocksr/control"), _("Access Control"), 30).leaf = true entry({"admin", "services", "shadowsocksr", "advanced"}, cbi("shadowsocksr/advanced"), _("Advanced Settings"), 50).leaf = true entry({"admin", "services", "shadowsocksr", "server"}, arcombine(cbi("shadowsocksr/server"), cbi("shadowsocksr/server-config")), _("SSR Server"), 60).leaf = true + entry({"admin", "services", "shadowsocksr", "component"}, cbi("shadowsocksr/component"), _("Component Update"), 65).leaf = true entry({"admin", "services", "shadowsocksr", "status"}, form("shadowsocksr/status"), _("Status"), 70).leaf = true entry({"admin", "services", "shadowsocksr", "check"}, call("check_status")) entry({"admin", "services", "shadowsocksr", "refresh"}, call("refresh_data")) entry({"admin", "services", "shadowsocksr", "subscribe"}, call("subscribe")) + entry({"admin", "services", "shadowsocksr", "component_local_status"}, call("component_local_status")).leaf = true + entry({"admin", "services", "shadowsocksr", "component_set_mirror"}, call("component_set_mirror")).leaf = true + entry({"admin", "services", "shadowsocksr", "component_status"}, call("component_status")).leaf = true + entry({"admin", "services", "shadowsocksr", "component_upgrade"}, call("component_upgrade")).leaf = true + entry({"admin", "services", "shadowsocksr", "geo_local_status"}, call("geo_local_status")).leaf = true + entry({"admin", "services", "shadowsocksr", "geo_status"}, call("geo_status")).leaf = true + entry({"admin", "services", "shadowsocksr", "geo_upgrade"}, call("geo_upgrade")).leaf = true entry({"admin", "services", "shadowsocksr", "checkport"}, call("check_port")) entry({"admin", "services", "shadowsocksr", "log"}, form("shadowsocksr/log"), _("Log"), 80).leaf = true entry({"admin", "services", "shadowsocksr", "get_log"}, call("get_log")).leaf = true entry({"admin", "services", "shadowsocksr", "clear_log"}, call("clear_log")).leaf = true entry({"admin", "services", "shadowsocksr", "run"}, call("act_status")) entry({"admin", "services", "shadowsocksr", "ping"}, call("act_ping")) + entry({"admin", "services", "shadowsocksr", "save_order"}, call("save_order")).leaf = true + entry({"admin", "services", "shadowsocksr", "delete_node"}, call("act_delete_node")).leaf = true + entry({"admin", "services", "shadowsocksr", "add_subscribe_item"}, call("add_subscribe_item")).leaf = true + entry({"admin", "services", "shadowsocksr", "delete_subscribe_item"}, call("delete_subscribe_item")).leaf = true + entry({"admin", "services", "shadowsocksr", "toggle_subscribe_item_enabled"}, call("toggle_subscribe_item_enabled")).leaf = true entry({"admin", "services", "shadowsocksr", "reset"}, call("act_reset")) entry({"admin", "services", "shadowsocksr", "restart"}, call("act_restart")) entry({"admin", "services", "shadowsocksr", "delete"}, call("act_delete")) - --[[ API ]] - entry({"admin", "services", "shadowsocksr", "add_node"}, call("act_add_node")).leaf = true - entry({"admin", "services", "shadowsocksr", "remove_node"}, call("act_remove")) - entry({"admin", "services", "shadowsocksr", "save_node_order"}, call("act_save_order")).leaf = true - entry({"admin", "services", "shadowsocksr", "get_now_use_node"}, call("act_get_now_use_node")).leaf = true - entry({'admin', 'services', "shadowsocksr", 'ip'}, call('check_ip')) -- 获取ip情况 + entry({"admin", "services", "shadowsocksr", "clash_panel"}, call("clash_panel")).leaf = true + entry({"admin", "services", "shadowsocksr", "clash_groups"}, call("clash_groups")).leaf = true + entry({"admin", "services", "shadowsocksr", "clash_switch"}, call("clash_switch")).leaf = true + entry({"admin", "services", "shadowsocksr", "clash_refresh"}, call("clash_refresh")).leaf = true + entry({"admin", "services", "shadowsocksr", "clash_reset_defaults"}, call("clash_reset_defaults")).leaf = true + entry({"admin", "services", "shadowsocksr", "clash_client_policies"}, call("clash_client_policies")).leaf = true + entry({"admin", "services", "shadowsocksr", "clash_client_rule_save"}, call("clash_client_rule_save")).leaf = true + entry({"admin", "services", "shadowsocksr", "clash_client_rule_clear"}, call("clash_client_rule_clear")).leaf = true --[[Backup]] entry({"admin", "services", "shadowsocksr", "backup"}, call("create_backup")).leaf = true + entry({'admin', 'services', "shadowsocksr", 'ip'}, call('check_ip')) -- 获取ip情况 end function check_site(host, port) @@ -137,34 +722,639 @@ function check_ip() end function subscribe() - luci.sys.call("/usr/bin/lua /usr/share/shadowsocksr/subscribe.lua >>/var/log/ssrplus.log") + nixio.fs.remove(SERVER_DETECT_CACHE) + local sid = luci.http.formvalue("sid") or "" + local subscribe_arg = sid ~= "" and (" " .. luci.util.shellquote(sid)) or "" + local ret = luci.sys.call(": > /var/log/ssrplus.log && /usr/bin/lua /usr/share/shadowsocksr/subscribe.lua" .. subscribe_arg .. " >>/var/log/ssrplus.log 2>&1") luci.http.prepare_content("application/json") - luci.http.write_json({ret = 1}) + luci.http.write_json({ret = ret}) +end + +function save_order() + local order = luci.http.formvalue("order") or "" + local page = parse_nonnegative_int(luci.http.formvalue("page")) or 1 + local page_size = parse_nonnegative_int(luci.http.formvalue("page_size")) or 0 + local sids = {} + local all_sections = {} + local server_sections = {} + local server_positions = {} + local section_index = {} + local page_start + local page_end + + for sid in order:gmatch("%S+") do + if uci:get("shadowsocksr", sid) == "servers" and not section_index[sid] then + sids[#sids + 1] = sid + section_index[sid] = true + end + end + + uci:foreach("shadowsocksr", nil, function(section) + all_sections[#all_sections + 1] = section[".name"] + if section[".type"] == "servers" then + server_sections[#server_sections + 1] = section[".name"] + server_positions[#server_positions + 1] = #all_sections + end + end) + + page_start = 1 + page_end = #server_sections + if page_size > 0 then + page_start = ((math.max(page, 1) - 1) * page_size) + 1 + page_end = math.min(page_start + page_size - 1, #server_sections) + end + + local page_sections = {} + local page_lookup = {} + for index = page_start, page_end do + local sid = server_sections[index] + if sid then + page_sections[#page_sections + 1] = sid + page_lookup[sid] = true + end + end + + local valid = #sids > 0 and #sids == #page_sections + if valid then + for _, sid in ipairs(sids) do + if not page_lookup[sid] then + valid = false + break + end + end + end + + if valid then + for offset, sid in ipairs(sids) do + local absolute_index = page_start + offset - 1 + local position = server_positions[absolute_index] + if not position then + valid = false + break + end + + local cmd = string.format( + "uci -q reorder %s=%d >/dev/null 2>&1", + shell_quote("shadowsocksr." .. sid), + position - 1 + ) + + if luci.sys.call(cmd) ~= 0 then + valid = false + break + end + all_sections[position] = sid + end + if valid then + valid = luci.sys.call("uci -q commit shadowsocksr >/dev/null 2>&1") == 0 + end + end + + luci.http.prepare_content("application/json") + luci.http.write_json({ + ret = valid and 1 or 0, + count = #sids, + page = page, + page_size = page_size + }) +end + +function act_delete_node() + local sid = luci.http.formvalue("sid") + + if not sid or sid == "" then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "missing sid" }) + return + end + + local del_cmd = luci.sys.call("uci -q delete shadowsocksr." .. sid) + if del_cmd ~= 0 then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "delete failed" }) + return + end + + local ret_cmd = luci.sys.call("uci -q commit shadowsocksr >/dev/null 2>&1") + if ret_cmd ~= 0 then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "commit failed" }) + return + end + + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 1, sid = sid }) +end + +function add_subscribe_item() + local sid = luci.sys.exec("uci add shadowsocksr server_subscribe_item"):gsub("%s+", "") + + if not sid or sid == "" then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "add failed" }) + return + end + + local alias = string.format("Subscribe %s", sid:sub(-4)) + + -- set enabled + local subscribe_enabled = luci.sys.call("uci -q set shadowsocksr." .. sid .. ".enabled=1") + if subscribe_enabled ~= 0 then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "set enabled failed" }) + return + end + + -- set alias + local subscribe_alias = luci.sys.call("uci -q set shadowsocksr." .. sid .. ".alias='" .. alias .. "'") + if subscribe_alias ~= 0 then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "set alias failed" }) + return + end + + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 1, sid = sid, alias = alias, enabled = "1" }) +end + +function delete_subscribe_item() + local sid = trim(luci.http.formvalue("sid")) + if sid == "" then + luci.http.status(400, "Bad Request") + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "missing sid" }) + return + end + + local delete_subscribe_set = luci.sys.call("uci -q delete shadowsocksr." .. sid .. " 2>/dev/null") + if delete_subscribe_set ~= 0 then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "delete failed" }) + return + end + + -- commit + local delete_subscribe_cmd = luci.sys.call("uci -q commit shadowsocksr >/dev/null 2>&1") + if delete_subscribe_cmd ~= 0 then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "commit failed" }) + return + end + + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 1, sid = sid }) +end + +function toggle_subscribe_item_enabled() + local sid = trim(luci.http.formvalue("sid")) + local field = luci.http.formvalue("field") + local value = luci.http.formvalue("value") + + -- 兼容旧调用方式(只传 enabled) + if not field then + field = "enabled" + value = luci.http.formvalue("enabled") == "1" and "1" or "0" + end + + -- 参数校验 + if sid == "" then + luci.http.status(400, "Bad Request") + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "missing sid" }) + return + end + + -- 检查 sid 对应的 section 类型 + if uci:get("shadowsocksr", sid) ~= "server_subscribe_item" then + luci.http.status(400, "Bad Request") + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "invalid_sid" }) + return + end + + -- 白名单:只允许以下字段 + local allowed_fields = { enabled = true, alias = true, url = true } + if not allowed_fields[field] then + luci.http.status(400, "Bad Request") + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "unsupported field" }) + return + end + + -- 处理字段值 + if field == "enabled" then + value = (value == "1" or value == "true") and "1" or "0" + elseif field == "alias" or field == "url" then + value = value and trim(value) or "" + end + + -- 转义 value 中的单引号(避免破坏 uci 命令) + local escaped_value = value:gsub("'", "'\\''") + + -- 使用外部 uci 命令设置值 + local set_cmd = string.format("uci -q set shadowsocksr.%s.%s='%s'", sid, field, escaped_value) + local set_ret = luci.sys.call(set_cmd .. " >/dev/null 2>&1") + if set_ret ~= 0 then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "uci set failed" }) + return + end + + -- 提交更改 + local ret_cmd = luci.sys.call("uci -q commit shadowsocksr >/dev/null 2>&1") + if ret_cmd ~= 0 then + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 0, error = "commit failed" }) + return + end + + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 1, sid = sid, field = field, value = value }) +end + +function component_status() + local component = luci.http.formvalue("component") + local data, status, err = read_component_state(component, "info") + if not data then + luci.http.status(status or 500, "Bad Request") + write_component_json({component = component, error = err or "bad_request"}) + return + end + write_component_json(data) +end + +function component_local_status() + local component = luci.http.formvalue("component") + local data, status, err = read_component_state(component, "local_info") + if not data then + luci.http.status(status or 500, "Bad Request") + write_component_json({component = component, error = err or "bad_request"}) + return + end + write_component_json(data) +end + +function component_set_mirror() + local mirror = luci.http.formvalue("mirror") or "direct" + local allowed = { + direct = true, + ghproxy = true, + ghproxy_cc = true, + ghfast = true, + jsdelivr = true + } + + if not allowed[mirror] then + mirror = "direct" + end + + uci:set("shadowsocksr", "@global[0]", "component_mirror", mirror) + uci:commit("shadowsocksr") + + luci.http.prepare_content("application/json") + luci.http.write_json({ ret = 1, mirror = mirror }) +end + +function component_upgrade() + local component = luci.http.formvalue("component") + local data, status, err = read_component_state(component, "upgrade") + if not data then + luci.http.status(status or 500, "Bad Request") + write_component_json({component = component, error = err or "bad_request", success = "0"}) + return + end + + local info = read_component_state(component, "info") + if info then + for key, value in pairs(info) do + if data[key] == nil or data[key] == "" then + data[key] = value + end + end + if data.success == "1" then + data.can_upgrade = info.can_upgrade + end + end + + write_component_json(data) +end + +function geo_status() + local component = luci.http.formvalue("component") + local data, status, err = read_geo_state(component, "info") + if not data then + luci.http.status(status or 500, "Bad Request") + write_geo_json({component = component, error = err or "bad_request"}) + return + end + write_geo_json(data) +end + +function geo_local_status() + local component = luci.http.formvalue("component") + local data, status, err = read_geo_state(component, "local_info") + if not data then + luci.http.status(status or 500, "Bad Request") + write_geo_json({component = component, error = err or "bad_request"}) + return + end + write_geo_json(data) +end + +function geo_upgrade() + local component = luci.http.formvalue("component") + local data, status, err = read_geo_state(component, "upgrade") + if not data then + luci.http.status(status or 500, "Bad Request") + write_geo_json({component = component, error = err or "bad_request", success = "0"}) + return + end + write_geo_json(data) end function act_status() local e = {} - e.running = luci.sys.call("busybox ps -w | grep ssr-retcp | grep -v grep >/dev/null") == 0 + e.running = global_client_running() + if e.running then + e.status_text = get_running_status_text() + end luci.http.prepare_content("application/json") luci.http.write_json(e) end +function clash_panel() + local sid = luci.http.formvalue("sid") + if uci:get("shadowsocksr", sid) ~= "servers" or uci:get("shadowsocksr", sid, "type") ~= "clash" then + luci.http.status(404, "Not Found") + return + end + luci.template.render("shadowsocksr/clash_panel", { + sid = sid, + alias = uci:get("shadowsocksr", sid, "alias") or sid + }) +end + +function clash_groups() + local sid = luci.http.formvalue("sid") + local groups = {} + local active_sid = resolve_active_clash_sid(sid) + local active = active_sid ~= nil + if active then + local raw = clash_api_request(active_sid, "GET", "/proxies") + if raw then + groups = parse_clash_groups(raw.body) + end + end + luci.http.prepare_content("application/json") + luci.http.write_json({ + active = active, + sid = active_sid, + groups = groups + }) +end + +function clash_switch() + local sid = luci.http.formvalue("sid") + local group = luci.http.formvalue("group") + local name = luci.http.formvalue("name") + if not sid or not group or not name then + luci.http.status(400, "Bad Request") + return + end + local active_sid = resolve_active_clash_sid(sid) + if not active_sid then + luci.http.status(409, "Conflict") + luci.http.prepare_content("application/json") + luci.http.write_json({success = false, message = "inactive"}) + return + end + local body = string.format('{"name":"%s"}', tostring(name):gsub('"', '\\"')) + local path = "/proxies/" .. urlencode(group) + local ret = clash_api_request(active_sid, "PUT", path, body) + luci.http.prepare_content("application/json") + luci.http.write_json({success = ret ~= nil and ret.code and ret.code >= 200 and ret.code < 300, sid = active_sid}) +end + +function clash_refresh() + local sid = luci.http.formvalue("sid") + if not sid or uci:get("shadowsocksr", sid) ~= "servers" or uci:get("shadowsocksr", sid, "type") ~= "clash" then + luci.http.status(400, "Bad Request") + luci.http.prepare_content("application/json") + luci.http.write_json({success = false}) + return + end + local cmd = string.format("/etc/init.d/shadowsocksr clash_cache %s >/dev/null 2>&1", luci.util.shellquote(sid)) + local ok = luci.sys.call(cmd) == 0 + local reapplied = false + if ok and is_active_clash_node(sid) then + luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &") + reapplied = true + end + luci.http.prepare_content("application/json") + luci.http.write_json({ + success = ok, + cached = nixio.fs.access(get_clash_cache_file(sid)), + reapplied = reapplied + }) +end + +function clash_reset_defaults() + local sid = luci.http.formvalue("sid") + if not sid or uci:get("shadowsocksr", sid) ~= "servers" or uci:get("shadowsocksr", sid, "type") ~= "clash" then + luci.http.status(400, "Bad Request") + luci.http.prepare_content("application/json") + luci.http.write_json({success = false}) + return + end + + local state_file = get_clash_state_file(sid) + local cleared = false + if nixio.fs.access(state_file) then + cleared = nixio.fs.remove(state_file) or false + else + cleared = true + end + + local reapplied = false + if cleared and is_active_clash_node(sid) then + luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &") + reapplied = true + end + + luci.http.prepare_content("application/json") + luci.http.write_json({ + success = cleared, + reapplied = reapplied + }) +end + +function clash_client_policies() + local sid = luci.http.formvalue("sid") + local active_sid = resolve_active_clash_sid(sid) + local groups = {} + if active_sid then + local raw = clash_api_request(active_sid, "GET", "/proxies") + if raw then + groups = parse_clash_groups(raw.body) + end + end + + local policies = {} + local seen = {} + for _, group in ipairs(groups or {}) do + if group.name and not seen[group.name] then + seen[group.name] = true + policies[#policies + 1] = { + name = group.name, + label = group.name, + type = group.type or "" + } + end + for _, proxy_name in ipairs(group.all or {}) do + if proxy_name and proxy_name ~= "" and not seen[proxy_name] then + seen[proxy_name] = true + policies[#policies + 1] = { + name = proxy_name, + label = proxy_name, + type = "proxy" + } + end + end + end + + table.sort(policies, function(a, b) + return tostring(a.label or a.name) < tostring(b.label or b.name) + end) + + luci.http.prepare_content("application/json") + luci.http.write_json({ + active = active_sid ~= nil, + sid = active_sid, + clients = collect_lan_clients(), + rules = read_clash_client_rules(sid), + policies = policies + }) +end + +function clash_client_rule_save() + local sid = luci.http.formvalue("sid") + local rows = {} + local max_rows = tonumber(luci.http.formvalue("count") or "0") or 0 + + for index = 1, math.min(max_rows, 256) do + local prefix = string.format("rule_%d_", index) + local ip_addr = normalize_client_ip(luci.http.formvalue(prefix .. "ip_addr")) + local enabled = luci.http.formvalue(prefix .. "enabled") == "1" and "1" or "0" + local remarks = trim(luci.http.formvalue(prefix .. "remarks")) + local policy_group = trim(luci.http.formvalue(prefix .. "policy_group")) + local client_mac = sanitize_mac(luci.http.formvalue(prefix .. "client_mac")) + + if ip_addr ~= "" and policy_group ~= "" then + rows[#rows + 1] = { + enabled = enabled, + remarks = remarks, + ip_addr = ip_addr, + client_mac = client_mac, + policy_group = policy_group + } + end + end + + if not sid or sid == "" or uci:get("shadowsocksr", sid) ~= "servers" or uci:get("shadowsocksr", sid, "type") ~= "clash" then + luci.http.status(400, "Bad Request") + luci.http.prepare_content("application/json") + luci.http.write_json({ success = false, error = "invalid_sid" }) + return + end + + write_clash_client_rules_csv(sid, rows) + + local active_sid = resolve_active_clash_sid(sid) + local current_sid = uci:get_first("shadowsocksr", "global", "global_server") + local reapplied = false + if sid and sid ~= "" and uci:get("shadowsocksr", sid) == "servers" + and uci:get("shadowsocksr", sid, "type") == "clash" + and current_sid == sid then + luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &") + reapplied = true + end + + luci.http.prepare_content("application/json") + luci.http.write_json({ + success = true, + count = #rows, + reapplied = reapplied, + rules = read_clash_client_rules(sid) + }) +end + +function clash_client_rule_clear() + local sid = luci.http.formvalue("sid") + + if not sid or sid == "" or uci:get("shadowsocksr", sid) ~= "servers" or uci:get("shadowsocksr", sid, "type") ~= "clash" then + luci.http.status(400, "Bad Request") + luci.http.prepare_content("application/json") + luci.http.write_json({ success = false, error = "invalid_sid" }) + return + end + + local csv_path = get_clash_client_rule_csv_path(sid) + if csv_path and nixio.fs.access(csv_path) then + nixio.fs.remove(csv_path) + end + + local current_sid = uci:get_first("shadowsocksr", "global", "global_server") + local reapplied = false + if sid and sid ~= "" and uci:get("shadowsocksr", sid) == "servers" + and uci:get("shadowsocksr", sid, "type") == "clash" + and current_sid == sid then + luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &") + reapplied = true + end + + luci.http.prepare_content("application/json") + luci.http.write_json({ + success = true, + reapplied = reapplied, + rules = {} + }) +end + function act_ping() local e = {} local domain = luci.http.formvalue("domain") local port = tonumber(luci.http.formvalue("port") or 0) local transport = (luci.http.formvalue("transport") or ""):lower() local wsPath = luci.http.formvalue("wsPath") or "" + local host = luci.http.formvalue("host") or "" + local tls_host = luci.http.formvalue("tlsHost") or "" local tls = luci.http.formvalue("tls") - local host = luci.http.formvalue("host") local type = (luci.http.formvalue("type") or ""):lower() local proto = (luci.http.formvalue("proto") or ""):lower() + local reality = luci.http.formvalue("reality") + local sid = luci.http.formvalue("sid") e.index = luci.http.formvalue("index") + if sid and sid ~= "" and uci:get("shadowsocksr", sid) == "servers" then + domain = uci:get("shadowsocksr", sid, "server") or domain + port = tonumber(uci:get("shadowsocksr", sid, "server_port") or port or 0) + transport = (uci:get("shadowsocksr", sid, "transport") or transport or ""):lower() + wsPath = uci:get("shadowsocksr", sid, "ws_path") or wsPath + host = uci:get("shadowsocksr", sid, "ws_host") or host + tls_host = uci:get("shadowsocksr", sid, "tls_host") or tls_host + tls = uci:get("shadowsocksr", sid, "tls") or tls + type = (uci:get("shadowsocksr", sid, "type") or type or ""):lower() + proto = (uci:get("shadowsocksr", sid, "v2ray_protocol") or proto or ""):lower() + reality = uci:get("shadowsocksr", sid, "reality") or reality + end + local is_ip = domain and domain:match("^%d+%.%d+%.%d+%.%d+$") + local probe_host = (tls_host ~= "" and tls_host) or (host ~= "" and host) or domain + local is_reality = (reality == "1" or reality == "true") + local prefers_handshake_latency = (type == "v2ray") and not is_reality -- 临时放行防火墙逻辑 - local use_nft = luci.sys.call("command -v nft >/dev/null") == 0 + local use_nft = use_fw4_backend() local iret = false if domain then if use_nft then @@ -173,46 +1363,52 @@ function act_ping() iret = luci.sys.call("ipset add ss_spec_wan_ac " .. domain .. " 2>/dev/null") == 0 end end - -- Hysteria2 节点检测 + -- Hysteria2 节点轻量 UDP 端口检测 if proto:find("hysteria2") or type:find("hysteria2") then - local node_id = e.index - -- 调用Shell测试脚本 - local cmd = string.format( - "/usr/share/shadowsocksr/hy2_test.sh url_test_hy2 %s", - node_id - ) - local res = luci.sys.exec(cmd) or "" - -- 解析结果 - local http_code, time_pre = string.match(res, "(%d+):([%d%.]+)") - if http_code == "200" or http_code == "204" then - e.socket = true - e.ping = math.floor(tonumber(time_pre or 0) * 1000) - else - e.socket = false + local udp_cmd = string.format("nping --udp -c 1 -p %d %s 2>/dev/null", port, domain) + local udp_raw = luci.sys.exec(udp_cmd) or "" + local udp_rtt = udp_raw:match("Avg rtt:%s*([0-9.]+)ms") + local udp_unreachable = udp_raw:match("[Pp]ort [Uu]nreachable") or udp_raw:match("ICMP") + local udp_sent = udp_raw:match("Raw packets sent:%s*1") + + -- UDP 服务通常不会主动回包,未收到应答不等于端口不可用。 + -- 仅在出现明显的不可达迹象时标记 fail,其余视为轻量可达。 + e.socket = (udp_unreachable == nil) and (udp_sent ~= nil) + e.ping = udp_rtt and normalize_ping_ms(udp_rtt) or nil + + if not e.ping then + local icmp_cmd = string.format("ping -c 1 -W 1 %s 2>/dev/null | grep -o 'time=[0-9.]*' | cut -d= -f2", domain) + e.ping = normalize_ping_ms(tonumber(luci.sys.exec(icmp_cmd))) + end + if not e.ping then e.ping = 0 end elseif transport == "ws" then -- WebSocket 探测 local result = "" local success = false + local icmp_cmd = string.format("ping -c 1 -W 1 %s 2>/dev/null | grep -o 'time=[0-9.]*' | cut -d= -f2", domain) + e.ping = normalize_ping_ms(tonumber(luci.sys.exec(icmp_cmd))) -- WebSocket 探测 (适用于域名,或带 SNI 的 IP) - if not is_ip or (host and host ~= "") then + if not is_ip or probe_host ~= domain then local resolve_arg = "" - local final_domain = domain - if is_ip and host and host ~= "" then + local final_domain = probe_host + if is_ip and probe_host and probe_host ~= "" then -- IP 模式下使用 --resolve 强制指定 SNI,解决 TLS 握手失败 - resolve_arg = string.format("--resolve '%s:%d:%s' ", host, port, domain) - final_domain = host + resolve_arg = string.format("--resolve '%s:%d:%s' ", probe_host, port, domain) end local prefix = (tls == '1') and "https://" or "http://" local address = prefix .. final_domain .. ':' .. port .. wsPath local cmd = string.format( "curl --http1.1 -m 2 -ksN -o /dev/null %s" .. "-w 'time_connect=%%{time_connect}\\nhttp_code=%%{http_code}' " .. + "%s" .. "-H 'Connection: Upgrade' -H 'Upgrade: websocket' " .. "-H 'Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==' " .. "-H 'Sec-WebSocket-Version: 13' '%s'", - resolve_arg, address + resolve_arg, + (probe_host and probe_host ~= "") and ("-H " .. luci.util.shellquote("Host: " .. probe_host) .. " ") or "", + address ) result = luci.sys.exec(cmd) or "" success = (string.match(result, "http_code=(%d+)") == "101") @@ -229,13 +1425,17 @@ function act_ping() --luci.sys.exec(string.format("echo 'Node %s (ws) failed deep test, using TCP fallback' >> /tmp/ping.log", domain)) end e.socket = success - -- 解析延迟 (优先用 curl 数据,失败则回退到 tcping) - local ping_time = tonumber(string.match(result, "time_connect=(%d+.%d%d%d)")) - if ping_time and ping_time > 0 then - e.ping = math.floor(ping_time * 1000) - else - local tcping_cmd = string.format("tcping -q -c 1 -t 1 -p %d %s 2>/dev/null | grep -o 'time=[0-9]*' | cut -d= -f2", port, domain) - e.ping = tonumber(luci.sys.exec(tcping_cmd)) or 0 + -- 延迟:优先 ping,再 curl,最后 nping tcp-connect + if not e.ping then + local ping_time = tonumber(string.match(result, "time_connect=(%d+.%d%d%d)")) + local appconnect_time = tonumber(string.match(result, "time_appconnect=(%d+.%d%d%d)")) + if appconnect_time and appconnect_time > 0 then + e.ping = normalize_ping_ms(appconnect_time, 1000) + elseif ping_time and ping_time > 0 then + e.ping = normalize_ping_ms(ping_time, 1000) + else + e.ping = detect_tcp_connect_ms(domain, port) or 0 + end end else -- 3. 非 WebSocket 节点的探测逻辑 (TCP / ICMP / UDP) @@ -247,12 +1447,19 @@ function act_ping() socket:close() end - -- 延迟:tcping -> ping -> nping(udp) - local tcping_cmd = string.format("tcping -q -c 1 -t 1 -p %d %s 2>/dev/null | grep -o 'time=[0-9]*' | cut -d= -f2", port, domain) - e.ping = tonumber(luci.sys.exec(tcping_cmd)) + if prefers_handshake_latency and (tls == "1" or tls_host ~= "" or proto == "vless" or proto == "vmess") then + e.ping = detect_tls_handshake_ms(domain, port, "", probe_host, domain, false) + end + + -- 延迟:优先真实握手,再 nping tcp-connect -> ping -> nping(udp) + if not e.ping then + if not is_reality then + e.ping = detect_tcp_connect_ms(domain, port) + end + end if not e.ping then local icmp_cmd = string.format("ping -c 1 -W 1 %s 2>/dev/null | grep -o 'time=[0-9.]*' | cut -d= -f2", domain) - e.ping = tonumber(luci.sys.exec(icmp_cmd)) + e.ping = normalize_ping_ms(tonumber(luci.sys.exec(icmp_cmd))) end if not e.ping then @@ -260,7 +1467,23 @@ function act_ping() local udp_res = luci.sys.exec(udp_cmd) if udp_res and udp_res ~= "" then local ping_num = tonumber(udp_res) - if ping_num then e.ping = math.floor(ping_num) end + if ping_num then e.ping = normalize_ping_ms(ping_num) end + end + end + + if (not e.ping or e.ping == 0) and domain and port > 0 then + local schemes = { "https", "http" } + for _, scheme in ipairs(schemes) do + local connect_cmd = string.format( + "curl -m 2 -ksS -o /dev/null -w 'time_connect=%%{time_connect}' %s://%s:%d 2>/dev/null", + scheme, domain, port + ) + local connect_res = luci.sys.exec(connect_cmd) or "" + local connect_time = tonumber(connect_res:match("time_connect=([0-9.]+)")) + if connect_time and connect_time > 0 then + e.ping = normalize_ping_ms(connect_time, 1000) + break + end end end end @@ -274,13 +1497,26 @@ function act_ping() end end + if sid and sid ~= "" then + save_detect_cache_entry(sid, { + server = domain or "", + port = port or 0, + type = type or "", + proto = proto or "", + socket = e.socket and true or false, + ping = tonumber(e.ping) or 0, + time = os.time() + }) + end + luci.http.prepare_content("application/json") luci.http.write_json(e) end function check_status() local e = {} - e.ret = luci.sys.call("/usr/bin/ssr-check www." .. luci.http.formvalue("set") .. ".com 80 3 1") + local target = luci.http.formvalue("set") or "" + e.ret = luci.sys.call("curl -m 3 -sS -o /dev/null http://www." .. target .. ".com >/dev/null 2>&1") luci.http.prepare_content("application/json") luci.http.write_json(e) end @@ -297,9 +1533,13 @@ function check_port() local s local server_name = "" local uci = require "luci.model.uci".cursor() - local use_nft = luci.sys.call("command -v nft >/dev/null") == 0 + local use_nft = use_fw4_backend() uci:foreach("shadowsocksr", "servers", function(s) + if s.type == "clash" then + retstring = retstring .. string.format("[%s] Clash panel node.
", s.alias or s[".name"]) + return + end if s.alias then server_name = s.alias elseif s.server and s.server_port then @@ -307,15 +1547,18 @@ function check_port() end -- 临时加入 set + local is_ipv6 = is_ipv6_address(s.server) local iret = false - if use_nft then - iret = luci.sys.call("nft add element inet ss_spec ss_spec_wan_ac { " .. s.server .. " } 2>/dev/null") == 0 - else - iret = luci.sys.call("ipset add ss_spec_wan_ac " .. s.server .. " 2>/dev/null") == 0 + if not is_ipv6 then + if use_nft then + iret = luci.sys.call("nft add element inet ss_spec ss_spec_wan_ac { " .. s.server .. " } 2>/dev/null") == 0 + else + iret = luci.sys.call("ipset add ss_spec_wan_ac " .. s.server .. " 2>/dev/null") == 0 + end end -- TCP 测试 - local socket = nixio.socket("inet", "stream") + local socket = nixio.socket(is_ipv6 and "inet6" or "inet", "stream") socket:setopt("socket", "rcvtimeo", 3) socket:setopt("socket", "sndtimeo", 3) local ret = socket:connect(s.server, s.server_port) @@ -347,109 +1590,15 @@ function act_reset() end function act_restart() + luci.sys.call("/etc/init.d/shadowsocksr restart > /dev/null 2>&1 &") luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr")) end function act_delete() - uci:delete_all("shadowsocksr", "servers", function(s) - if s.hashkey or s.isSubscribe then - return true - else - return false - end - end) - uci:commit("shadowsocksr") - for file in nixio.fs.glob("/tmp/sub_md5_*") do - nixio.fs.remove(file) - end - luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &") + luci.sys.call("/etc/init.d/shadowsocksr restart > /dev/null 2>&1 &") luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers")) end -function act_add_node() - local redirect = luci.http.formvalue("redirect") - local used_sid = {} - local next_sid = 1 - - uci:foreach("shadowsocksr", "servers", function(s) - local num = s[".name"]:match("^cfg(%x%x)") - if num then - local n = tonumber(num, 16) - used_sid[n] = true - end - end) - - local function get_next_sid() - while used_sid[next_sid] do - next_sid = next_sid + 1 - end - used_sid[next_sid] = true - return next_sid - end - - local sid = uci:section("shadowsocksr", "servers", nil) - local suffix = sid:sub(-4) - uci:delete("shadowsocksr", sid) - - local id = get_next_sid() - local cfgid = string.format("cfg%02x%s", id, suffix) - uci:section("shadowsocksr", "servers", cfgid) - uci_save(uci, "shadowsocksr") - - if redirect == "1" then - luci.http.redirect(url("servers", cfgid)) - else - luci.http.write_json({ result = cfgid }) - end -end - -function act_remove() - local id = luci.http.formvalue("id") - if id then - uci:delete("shadowsocksr", id) - uci:commit("shadowsocksr") - end - luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers")) -end - -function act_save_order() - local ids = luci.http.formvalue("ids") or "" - local new_order = {} - for id in ids:gmatch("([^,]+)") do - new_order[#new_order + 1] = id - end - - for idx, name in ipairs(new_order) do - luci.sys.call(string.format("uci -q reorder %s.%s=%d", "shadowsocksr", name, idx - 1)) - end - - sh_uci_commit("shadowsocksr") - luci.http.write_json({ status = "ok" }) -end - -function act_get_now_use_node() - local result = {} - local tcp_node = uci:get_first("shadowsocksr", "global", "global_server") - if tcp_node then - result["TCP"] = tcp_node - end - local udp_node = uci:get_first("shadowsocksr", "global", "udp_relay_server") - if udp_node then - result["UDP"] = udp_node - end - local netflix_node = uci:get_first("shadowsocksr", "global", "netflix_server") - if netflix_node then - result["netflix"] = netflix_node - end - local socks5_node = uci:get_first("shadowsocksr", "socks5_proxy", "server") - if socks5_node then - result["socks5"] = socks5_node - end - - luci.http.prepare_content("application/json") - luci.http.write_json(result) -end - function get_log() luci.http.write(luci.sys.exec("[ -f '/var/log/ssrplus.log' ] && cat /var/log/ssrplus.log")) end diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua index 2e5d38aff..a1023748b 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua @@ -1,6 +1,7 @@ local m, s, o local cbi = require "luci.cbi" local uci = require "luci.model.uci".cursor() +local URL = require "url" -- 获取 LAN IP 地址 function lanip() @@ -30,16 +31,32 @@ local lan_ip = lanip() local server_table = {} local type_table = {} local function is_finded(e) - return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= "" + return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= "" +end + +local function clash_display_name(s) + if s.type ~= "clash" or not s.clash_url or s.clash_url == "" then + return nil + end + local ok, parsed = pcall(URL.parse, s.clash_url) + if ok and parsed and parsed.host then + return "[CLASH]:" .. parsed.host + end + return "[CLASH]" end uci:foreach("shadowsocksr", "servers", function(s) - if s.alias then + if s.type ~= "tun" and s.alias then server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias} - elseif s.server and s.server_port then + elseif s.type ~= "tun" and s.server and s.server_port then server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port} + elseif s.type ~= "tun" then + local display_name = clash_display_name(s) + if display_name then + server_table[s[".name"]] = display_name + end end - if s.type then + if s.type and s.type ~= "tun" then type_table[s[".name"]] = s.type end end) @@ -79,6 +96,13 @@ o.datatype = "uinteger" o:depends("enable_switch", "1") o.default = 3 +s:append(cbi.Template("shadowsocksr/advanced_switch_compact")) + +o = s:option(Value, "default_node_local_port", translate("Default Node Local Port")) +o.datatype = "port" +o.default = 1234 +o.rmempty = false + o = s:option(Value, "gfwlist_url", translate("gfwlist Update url")) o:value("https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt", translate("v2fly/domain-list-community")) o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat")) @@ -92,145 +116,6 @@ o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang.CN.CIDR")) o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("china-operator-ip")) o.default = "https://ispip.clang.cn/all_cn.txt" -o = s:option(Flag, "netflix_enable", translate("Enable Netflix Mode")) -o.description = translate("When disabled shunt mode, will same time stopped shunt service.") -o.rmempty = false - -o = s:option(Value, "nfip_url", translate("nfip_url")) -o:value("https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt", translate("Netflix IP Only")) -o:value("https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/getflix.txt", translate("Netflix and AWS")) -o.default = "https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt" -o.description = translate("Customize Netflix IP Url") -o:depends("netflix_enable", "1") - -o = s:option(ListValue, "shunt_dns_mode", translate("DNS Query Mode For Shunt Mode")) -if is_finded("dns2socks") then - o:value("1", translate("Use DNS2SOCKS query and cache")) -end -if is_finded("dns2socks-rust") then - o:value("2", translate("Use DNS2SOCKS-RUST query and cache")) -end -if is_finded("mosdns") then - o:value("3", translate("Use MosDNS query")) -end -if is_finded("dnsproxy") then - o:value("4", translate("Use DNSPROXY query and cache")) -end -if is_finded("chinadns-ng") then - o:value("5", translate("Use ChinaDNS-NG query and cache")) -end -o:depends("netflix_enable", "1") -o.default = 1 - -o = s:option(Value, "shunt_dnsserver", translate("Anti-pollution DNS Server For Shunt Mode")) -o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)")) -o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)")) -o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)")) -o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)")) -o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)")) -o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)")) -o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)")) -o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)")) -o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)")) -o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)")) -o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)")) -o:depends("shunt_dns_mode", "1") -o:depends("shunt_dns_mode", "2") -o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") -o.datatype = "ip4addrport" - -o = s:option(Value, "shunt_mosdns_dnsserver", translate("Anti-pollution DNS Server")) -o:value("tcp://8.8.4.4:53,tcp://8.8.8.8:53", translate("Google Public DNS")) -o:value("tcp://208.67.222.222:53,tcp://208.67.220.220:53", translate("OpenDNS")) -o:value("tcp://209.244.0.3:53,tcp://209.244.0.4:53", translate("Level 3 Public DNS-1 (209.244.0.3-4)")) -o:value("tcp://4.2.2.1:53,tcp://4.2.2.2:53", translate("Level 3 Public DNS-2 (4.2.2.1-2)")) -o:value("tcp://4.2.2.3:53,tcp://4.2.2.4:53", translate("Level 3 Public DNS-3 (4.2.2.3-4)")) -o:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS")) -o:depends("shunt_dns_mode", "3") -o.description = translate("Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT (tcp://8.8.8.8 or tls://dns.google:853)") - -o = s:option(Flag, "shunt_mosdns_ipv6", translate("Disable IPv6 In MosDNS Query Mode (Shunt Mode)")) -o:depends("shunt_dns_mode", "3") -o.rmempty = false -o.default = "0" - -if is_finded("dnsproxy") then - o = s:option(ListValue, "shunt_parse_method", translate("Select DNS parse Mode")) - o.description = translate( - "" - ) - o:value("single_dns", translate("Set Single DNS")) - o:value("parse_file", translate("Use DNS List File")) - o:depends("shunt_dns_mode", "4") - o.rmempty = true - o.default = "single_dns" - - o = s:option(Value, "dnsproxy_shunt_forward", translate("Anti-pollution DNS Server")) - o:value("sdns://AgUAAAAAAAAABzguOC40LjQgsKKKE4EwvtIbNjGjagI2607EdKSVHowYZtyvD9iPrkkHOC44LjQuNAovZG5zLXF1ZXJ5", translate("Google DNSCrypt SDNS")) - o:value("sdns://AgcAAAAAAAAAACC2vD25TAYM7EnyCH8Xw1-0g5OccnTsGH9vQUUH0njRtAxkbnMudHduaWMudHcKL2Rucy1xdWVyeQ", translate("TWNIC-101 DNSCrypt SDNS")) - o:value("sdns://AgcAAAAAAAAADzE4NS4yMjIuMjIyLjIyMiAOp5Svj-oV-Fz-65-8H2VKHLKJ0egmfEgrdPeAQlUFFA8xODUuMjIyLjIyMi4yMjIKL2Rucy1xdWVyeQ", translate("dns.sb DNSCrypt SDNS")) - o:value("sdns://AgMAAAAAAAAADTE0OS4xMTIuMTEyLjkgsBkgdEu7dsmrBT4B4Ht-BQ5HPSD3n3vqQ1-v5DydJC8SZG5zOS5xdWFkOS5uZXQ6NDQzCi9kbnMtcXVlcnk", translate("Quad9 DNSCrypt SDNS")) - o:value("sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", translate("AdGuard DNSCrypt SDNS")) - o:value("sdns://AgcAAAAAAAAABzEuMC4wLjGgENk8mGSlIfMGXMOlIlCcKvq7AVgcrZxtjon911-ep0cg63Ul-I8NlFj4GplQGb_TTLiczclX57DvMV8Q-JdjgRgSZG5zLmNsb3VkZmxhcmUuY29tCi9kbnMtcXVlcnk", translate("Cloudflare DNSCrypt SDNS")) - o:value("sdns://AgcAAAAAAAAADjEwNC4xNi4yNDkuMjQ5ABJjbG91ZGZsYXJlLWRucy5jb20KL2Rucy1xdWVyeQ", translate("cloudflare-dns.com DNSCrypt SDNS")) - o:depends("shunt_parse_method", "single_dns") - o.description = translate("Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-query and other format).") - - o = s:option(ListValue, "shunt_upstreams_logic_mode", translate("Defines the upstreams logic mode")) - o.description = translate( - "
    " .. - "
  • " .. translate("Defines the upstreams logic mode, possible values: load_balance, parallel, fastest_addr (default: load_balance).") .. "
  • " .. "
  • " .. translate("When two or more DNS servers are deployed, enable this function.") .. "
  • " .. - "
" - ) - o:value("load_balance", translate("load_balance")) - o:value("parallel", translate("parallel")) - o:value("fastest_addr", translate("fastest_addr")) - o:depends("shunt_parse_method", "parse_file") - o.rmempty = true - o.default = "load_balance" - - o = s:option(Flag, "shunt_dnsproxy_ipv6", translate("Disable IPv6 query mode")) - o.description = translate("When disabled, all AAAA requests are not resolved.") - o:depends("shunt_parse_method", "single_dns") - o:depends("shunt_parse_method", "parse_file") - o.rmempty = false - o.default = "1" -end - -if is_finded("chinadns-ng") then - o = s:option(Value, "chinadns_ng_shunt_dnsserver", translate("Anti-pollution DNS Server For Shunt Mode")) - o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)")) - o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)")) - o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)")) - o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)")) - o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)")) - o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)")) - o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)")) - o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)")) - o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)")) - o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)")) - o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)")) - o:depends("shunt_dns_mode", "5") - o.description = translate( - "
    " .. - "
  • " .. translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") .. "
  • " .. - "
  • " .. translate("Muitiple DNS server can saperate with ','") .. "
  • " .. - "
" - ) - - o = s:option(ListValue, "chinadns_ng_shunt_proto", translate("ChinaDNS-NG shunt query protocol")) - o:value("none", translate("UDP/TCP upstream")) - o:value("tcp", translate("TCP upstream")) - o:value("udp", translate("UDP upstream")) - o:value("tls", translate("DoT upstream (Need use wolfssl version)")) - o:depends("shunt_dns_mode", "5") -end - o = s:option(Flag, "apple_optimization", translate("Apple domains optimization"), translate("For Apple domains equipped with Chinese mainland CDN, always responsive to Chinese CDN IP addresses")) o.rmempty = false o.default = "1" @@ -266,45 +151,13 @@ end -- [[ SOCKS5 Proxy ]]-- s = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server")) s.anonymous = true +-- s.description = translate("Only Same as Global Server is supported here. If the current main program supports a built-in SOCKS5 listener, it will be enabled in-process; otherwise SSR Plus will fall back to microsocks.") -- Enable/Disable Option o = s:option(Flag, "enabled", translate("Enable")) o.default = 0 o.rmempty = false --- Server Selection -o = s:option(ListValue, "server", translate("Server")) -o:value("same", translate("Same as Global Server")) -for _, key in pairs(key_table) do - o:value(key, server_table[key]) -end -o.default = "same" -o.rmempty = false - --- Dynamic value handling based on enabled/disabled state -o.cfgvalue = function(self, section) - local enabled = m:get(section, "enabled") - if enabled == "0" then - return m:get(section, "old_server") - end - return Value.cfgvalue(self, section)-- Default to `same` when enabled -end - -o.write = function(self, section, value) - local enabled = m:get(section, "enabled") - if enabled == "0" then - local old_server = Value.cfgvalue(self, section) - if old_server ~= "nil" then - m:set(section, "old_server", old_server) - end - m:set(section, "server", "nil") - else - m:del(section, "old_server") - -- Write the value normally when enabled - Value.write(self, section, value) - end -end - -- Socks Auth if is_finded("xray") then o = s:option(ListValue, "socks5_auth", translate("Socks5 Auth Mode"), translate("Socks protocol auth methods, default:noauth.")) @@ -312,13 +165,6 @@ o.default = "noauth" o:value("noauth", "NOAUTH") o:value("password", "PASSWORD") o.rmempty = true -for key, server_type in pairs(type_table) do - if server_type == "v2ray" then - -- 如果服务器类型是 v2ray,则设置依赖项显示 - o:depends("server", key) - end -end -o:depends({server = "same", disable = true}) -- Socks User o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when Socks5 Auth Mode is password valid, Mandatory.")) @@ -335,13 +181,6 @@ o:depends("socks5_auth", "password") o = s:option(Flag, "socks5_mixed", translate("Enabled Mixed"), translate("Mixed as an alias of socks, default:Enabled.")) o.default = "1" o.rmempty = false -for key, server_type in pairs(type_table) do - if server_type == "v2ray" then - -- 如果服务器类型是 v2ray,则设置依赖项显示 - o:depends("server", key) - end -end -o:depends({server = "same", disable = true}) end -- Local Port @@ -350,6 +189,37 @@ o.datatype = "port" o.default = 1080 o.rmempty = false +if is_finded("3proxy") then + -- [[ HTTP/HTTPS Proxy ]]-- + s = m:section(TypedSection, "http_proxy", translate("Global HTTP/HTTPS Proxy Server")) + s.anonymous = true + -- s.description = translate("Only Same as Global Server is supported here. HTTP/HTTPS proxy service is provided through 3proxy.") + + o = s:option(Flag, "enabled", translate("Enable")) + o.default = 0 + o.rmempty = false + + o = s:option(ListValue, "http_auth", translate("HTTP Auth Mode"), translate("HTTP proxy auth method, default:none.")) + o.default = "none" + o:value("none", "NONE") + o:value("password", "PASSWORD") + o.rmempty = false + + o = s:option(Value, "http_user", translate("HTTP User"), translate("Only when HTTP Auth Mode is password valid, Mandatory.")) + o.rmempty = true + o:depends("http_auth", "password") + + o = s:option(Value, "http_pass", translate("HTTP Password"), translate("Only when HTTP Auth Mode is password valid, Not mandatory.")) + o.password = true + o.rmempty = true + o:depends("http_auth", "password") + + o = s:option(Value, "local_port", translate("Local Port")) + o.datatype = "port" + o.default = 3128 + o.rmempty = false +end + -- [[ fragmen Settings ]]-- if is_finded("xray") then s = m:section(TypedSection, "global_xray_fragment", translate("Xray Fragment Settings")) diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua index ea91a088a..d271f2140 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua @@ -20,7 +20,7 @@ local xray_version_val = 0 -- 确保正确判断程序是否存在 local function is_finded(e) - return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= "" + return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= "" end local function is_installed(e) @@ -142,16 +142,14 @@ local function set_apply_on_parse(map) local old = map.on_after_save map.on_after_save = function(self) if old then old(self) end - map:set("@global[0]", "timestamp", os.time()) + --map:set("@global[0]", "timestamp", os.time()) end end end local has_ss_rust = is_finded("sslocal") or is_finded("ssserver") -local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local") -local has_trojan = is_finded("trojan") +local has_mihomo = is_finded("mihomo") local has_xray = is_finded("xray") -local has_hysteria2 = is_finded("hysteria") local server_table = {} local encrypt_methods = { @@ -256,6 +254,56 @@ local tls_flows = { "none" } +local function migrate_xray_protocol_nodes() + local changed = false + + uci:foreach("shadowsocksr", "servers", function(section) + local sid = section[".name"] + local stype = section.type + local proto = section.v2ray_protocol + local escaped_sid = luci.util.shellquote(sid) + + if stype == "ss" or stype == "ss-libev" then + if has_mihomo then + luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss'", escaped_sid)) + changed = true + elseif has_ss_rust then + luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss-rust'", escaped_sid)) + changed = true + elseif has_xray then + luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='shadowsocks'", escaped_sid, escaped_sid)) + changed = true + end + elseif stype == "v2ray" and proto == "shadowsocks" and has_mihomo then + luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss' && " .. "uci delete shadowsocksr.%s.v2ray_protocol", escaped_sid, escaped_sid)) + changed = true + end + if stype == "hysteria2" then + luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='hysteria2'", escaped_sid, escaped_sid)) + changed = true + elseif stype == "trojan" then + luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='trojan'", escaped_sid, escaped_sid)) + changed = true + end + end) + local subscribe_sid = uci:get_first("shadowsocksr", "server_subscribe") + if subscribe_sid then + local old_options = {"xray_hy2_type", "xray_tj_type", "ss_type"} + local escaped_sub_sid = luci.util.shellquote(subscribe_sid) + for _, opt in ipairs(old_options) do + if uci:get("shadowsocksr", subscribe_sid, opt) then + luci.sys.call(string.format("uci delete shadowsocksr.%s.%s", escaped_sub_sid, opt)) + changed = true + end + end + end + if changed then + luci.sys.call("uci commit shadowsocksr") + end +end + +migrate_xray_protocol_nodes() + m = Map("shadowsocksr", translate("Edit ShadowSocksR Server")) m.redirect = url("servers") if not sid or m.uci:get("shadowsocksr", sid) ~= "servers" then @@ -264,6 +312,14 @@ if not sid or m.uci:get("shadowsocksr", sid) ~= "servers" then end -- 保存&应用成功后跳转到节点列表 set_apply_on_parse(m) +local old_after_save = m.on_after_save +m.on_after_save = function(self) + if old_after_save then old_after_save(self) end + local node_type = self.uci:get("shadowsocksr", sid, "type") + if node_type == "clash" then + luci.sys.call(string.format("/etc/init.d/shadowsocksr clash_cache %s >/dev/null 2>&1 &", sid)) + end +end -- [[ Servers Setting ]]-- s = m:section(NamedSection, sid, "servers") @@ -275,88 +331,27 @@ o.rawhtml = true o.template = "shadowsocksr/ssrurl" o.value = sid --- 新增一个选择框,用于选择 Xray 或 Hysteria2 核心 -o = s:option(ListValue, "_xray_hy2_type", string.format("%s", translatef("%s Node Use Type", "Hysteria2"))) -o.description = translate("The configured type also applies to the core specified when manually importing nodes.") --- 注意:Auto 选项使用特殊字符串 "__auto__" 而不是空字符串 -o:value("__auto__", translate("Auto")) -if has_hysteria2 then - o:value("hysteria2", translate("Hysteria2")) -end -if has_xray then - o:value("v2ray", translate("Xray (Hysteria2)")) -end --- 读取全局 xray_hy2_type -o.cfgvalue = function(self, section) - local val = uci:get("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type") - if val == nil or val == "" then - return "__auto__" -- 对应 Auto 选项 - end - return val -end -o.rmempty = true --- 保存时更新全局配置 -o.write = function(self, section, value) - if value == "__auto__" then - -- 删除全局配置 - uci:delete("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type") - else - -- 设置具体值 - uci:set("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type", value) - end -end - -- 新增一个选择框,用于选择 Xray 或 Trojan 核心 -o = s:option(ListValue, "_xray_tj_type", string.format("%s", translatef("%s Node Use Type", "Trojan"))) -o.description = translate("The configured type also applies to the core specified when manually importing nodes.") --- 注意:Auto 选项使用特殊字符串 "__auto__" 而不是空字符串 -o:value("__auto__", translate("Auto")) -if has_hysteria2 then - o:value("trojan", translate("Trojan")) -end -if has_xray then - o:value("v2ray", translate("Xray (Trojan)")) -end --- 读取全局 xray_tj_type -o.cfgvalue = function(self, section) - local val = uci:get("shadowsocksr", "@server_subscribe[0]", "xray_tj_type") - if val == nil or val == "" then - return "__auto__" -- 对应 Auto 选项 - end - return val -end -o.rmempty = true --- 保存时更新全局配置 -o.write = function(self, section, value) - if value == "__auto__" then - -- 删除全局配置 - uci:delete("shadowsocksr", "@server_subscribe[0]", "xray_tj_type") - else - -- 设置具体值 - uci:set("shadowsocksr", "@server_subscribe[0]", "xray_tj_type", value) - end -end - o = s:option(ListValue, "type", translate("Server Node Type")) -if is_finded("xray") or is_finded("v2ray") then +if is_finded("xray") then o:value("v2ray", translate("V2Ray/XRay")) end if is_finded("ssr-redir") then o:value("ssr", translate("ShadowsocksR")) end -if has_ss_rust or has_ss_libev then - o:value("ss", translate("ShadowSocks")) +if has_mihomo then + o:value("ss", translate("ShadowSocks")) end -if is_finded("trojan") then - o:value("trojan", translate("Trojan")) +if has_ss_rust then + o:value("ss-rust", translate("ShadowSocks")) end if is_finded("naive") then o:value("naiveproxy", translate("NaiveProxy")) end -if is_finded("hysteria") then - o:value("hysteria2", translate("Hysteria2")) +if is_finded("mihomo") then + o:value("clash", translate("Clash/Mihomo")) end -if is_finded("tuic-client") then +if is_finded("mihomo") then o:value("tuic", translate("TUIC")) end if is_finded("shadow-tls") and is_finded("sslocal") then @@ -365,78 +360,51 @@ end if is_finded("ipt2socks") then o:value("socks5", translate("Socks5")) end -if is_finded("redsocks2") then - o:value("tun", translate("Network Tunnel")) -end local old_cfgvalue = o.cfgvalue o.cfgvalue = function(self, section) - local val = self.map.uci:get("shadowsocksr", section, "type") - if val == "ss-rust" or val == "ss-libev" then - return "ss" - end - if old_cfgvalue then + local val = self.map.uci:get("shadowsocksr", section, "type") + if old_cfgvalue then return old_cfgvalue(self, section) - end - return val + end + return val end --- 重写 write,当用户选择 "ss" 时不写入(由 _ss_core 负责写入具体核心) -local old_write = o.write -o.write = function(self, section, value) - if value == "ss" then - return -- 不做任何写入,等待 _ss_core 写入 - end - if old_write then - old_write(self, section, value) - else - self.map.uci:set("shadowsocksr", section, "type", value) - end -end - o.description = translate("Using incorrect encryption mothod may causes service fail to start") o = s:option(Value, "alias", translate("Alias(optional)")) -o = s:option(ListValue, "iface", translate("Network interface to use")) -for _, e in ipairs(luci.sys.net.devices()) do - if e ~= "lo" then - o:value(e) +local function clash_source_formvalue(map, section, option) + local value = map:formvalue("cbid." .. map.config .. "." .. section .. "." .. option) + if value == nil then + value = map.uci:get(map.config, section, option) end + return trim(value or "") end -o:depends("type", "tun") -o.description = translate("Redirect traffic to this network interface") --- 新增一个选择框,用于选择 Shadowsocks 具体版本(仅当节点类型为 ss 或其具体子类型时显示) -o = s:option(ListValue, "_ss_core", string.format("%s", translatef("%s Node Use Version", "ShadowSocks"))) -o.description = translate("Selection ShadowSocks Node Use Version.") -if has_ss_rust then - o:value("ss-rust", translate("ShadowSocks-rust Version")) +local function validate_clash_source(self, value, section) + local clash_url = clash_source_formvalue(self.map, section, "clash_url") + local clash_path = clash_source_formvalue(self.map, section, "clash_path") + if clash_url == "" and clash_path == "" then + return nil, translate("Please specify either a Clash subscription URL or a local YAML path.") + end + return value end -if has_ss_libev then - o:value("ss-libev", translate("ShadowSocks-libev Version")) -end -o.cfgvalue = function(self, section) - -- 读取当前节点的 type 值,如果已经是具体核心则显示对应的选项 - local node_type = self.map.uci:get("shadowsocksr", section, "type") - if node_type == "ss-rust" or node_type == "ss-libev" then - return node_type - end - -- 如果全局 ss_type 有值且为具体核心则返回该值 - local ss_type = self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "ss_type") - if ss_type == "ss-rust" or ss_type == "ss-libev" then - return ss_type - end - -- 如果节点 type 是旧的 "ss",则返回空,手动选择 - return nil -end --- 显示条件:当节点类型为 "ss" 或其具体核心时显示 -o:depends("type", "ss") + +o = s:option(Value, "clash_url", translate("Clash Subscription URL")) +o.placeholder = "https://example.com/config.yaml" o.rmempty = true --- 保存时,将选择的值直接写入当前节点的 type 字段 -o.write = function(self, section, value) - if value and value ~= "" then - self.map.uci:set("shadowsocksr", section, "type", value) - end -end +o:depends("type", "clash") +o.validate = validate_clash_source + +o = s:option(Value, "clash_path", translate("Clash YAML Path")) +o.placeholder = "/etc/ssrplus/clash/custom.yaml" +o.rmempty = true +o:depends("type", "clash") +o.validate = validate_clash_source + +o = s:option(Value, "clash_user_agent", translate("Clash User-Agent")) +o.default = "clash" +o.rmempty = false +o:depends("type", "clash") o = s:option(ListValue, "v2ray_protocol", translate("V2Ray/XRay protocol")) o:value("vless", translate("VLESS")) @@ -454,30 +422,54 @@ o:value("http", translate("HTTP")) o:depends("type", "v2ray") o = s:option(Value, "server", translate("Server Address")) -o.datatype = "host" +o.datatype = "or(host,ip6addr)" o.rmempty = false o:depends("type", "ssr") o:depends("type", "ss") -o:depends("type", "v2ray") +o:depends("type", "ss-rust") o:depends("type", "trojan") o:depends("type", "naiveproxy") o:depends("type", "hysteria2") o:depends("type", "tuic") o:depends("type", "shadowtls") o:depends("type", "socks5") +local protocols = s.fields["v2ray_protocol"].keylist +if protocols and type(protocols) == "table" and #protocols > 0 then + for _, proto in ipairs(protocols) do + if not proto:find("^_") then + if proto == "hysteria2" then + o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false}) + else + o:depends({type = "v2ray", v2ray_protocol = proto}) + end + end + end +end o = s:option(Value, "server_port", translate("Server Port")) o.datatype = "port" o.rmempty = true o:depends("type", "ssr") o:depends("type", "ss") -o:depends("type", "v2ray") +o:depends("type", "ss-rust") o:depends("type", "trojan") o:depends("type", "naiveproxy") o:depends("type", "hysteria2") o:depends("type", "tuic") o:depends("type", "shadowtls") o:depends("type", "socks5") +local protocols = s.fields["v2ray_protocol"].keylist +if protocols and type(protocols) == "table" and #protocols > 0 then + for _, proto in ipairs(protocols) do + if not proto:find("^_") then + if proto == "hysteria2" then + o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false}) + else + o:depends({type = "v2ray", v2ray_protocol = proto}) + end + end + end +end o = s:option(Flag, "auth_enable", translate("Enable Authentication")) o.rmempty = false @@ -498,6 +490,7 @@ o.password = true o.rmempty = true o:depends("type", "ssr") o:depends("type", "ss") +o:depends("type", "ss-rust") o:depends("type", "trojan") o:depends("type", "naiveproxy") o:depends("type", "shadowtls") @@ -524,6 +517,7 @@ for _, v in ipairs(encrypt_methods_ss) do end end o.rmempty = true +o:depends("type", "ss-rust") o:depends("type", "ss") o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"}) @@ -542,22 +536,30 @@ o.default = "1" o = s:option(Flag, "enable_plugin", translate("Enable Plugin")) o.rmempty = true o:depends("type", "ss") +o:depends("type", "ss-rust") o.default = "0" -- Shadowsocks Plugin o = s:option(ListValue, "plugin", translate("Obfs")) o:value("none", translate("None")) -if is_finded("obfs-local") then +if has_mihomo or is_finded("obfs-local") then o:value("obfs-local", translate("obfs-local")) end -if is_finded("v2ray-plugin") then +if has_mihomo or is_finded("v2ray-plugin") then o:value("v2ray-plugin", translate("v2ray-plugin")) end +if has_mihomo then + o:value("gost-plugin", translate("gost-plugin")) +end if is_finded("xray-plugin") then o:value("xray-plugin", translate("xray-plugin")) end -if is_finded("shadow-tls") then - o:value("shadow-tls", translate("shadow-tls")) +if has_mihomo or is_finded("shadow-tls") then + o:value("shadow-tls", translate("Shadow-TLS")) +end +if has_mihomo then + o:value("restls", translate("restls")) + o:value("kcptun", translate("kcptun")) end o:value("custom", translate("Custom")) o.rmempty = true @@ -593,6 +595,21 @@ o:depends("type", "ssr") -- [[ Hysteria2 ]]-- +o = s:option(Flag, "hysteria2_realms", translate("Hysteria2 Realms")) +o.default = "0" +if xray_version_val > 260509 then + o:depends({type = "v2ray", v2ray_protocol = "hysteria2"}) +else + o:depends({type = "v2ray", v2ray_protocol = "__hide"}) +end + +o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") +o:depends("hysteria2_realms", true) + +o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN")) +o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } +o:depends("hysteria2_realms", true) + o = s:option(Value, "hy2_auth", translate("Users Authentication")) o:depends("type", "hysteria2") o:depends({type = "v2ray", v2ray_protocol = "hysteria2"}) @@ -601,7 +618,7 @@ o.rmempty = false o = s:option(Flag, "flag_port_hopping", translate("Enable Port Hopping")) o:depends("type", "hysteria2") -o:depends({type = "v2ray", v2ray_protocol = "hysteria2"}) +o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false}) o.rmempty = true o.default = "0" @@ -643,17 +660,30 @@ o.rmempty = true o.default = "0" o = s:option(Value, "obfs_type", translate("Obfuscation Type")) +o:value("", translate("Disable")) +o:value("salamander") +o:value("gecko") +o.rmempty = true o:depends({type = "hysteria2", flag_obfs = true}) o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true}) -o.rmempty = true -o.placeholder = "salamander" o = s:option(Value, "salamander", translate("Obfuscation Password")) -o:depends({type = "hysteria2", flag_obfs = true}) -o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true}) o.password = true o.rmempty = true -o.placeholder = "cry_me_a_r1ver" +o:depends({type = "hysteria2", flag_obfs = true}) +local obfs = s.fields["obfs_type"].keylist +if obfs and type(obfs) == "table" and #obfs > 0 then + for _, v in ipairs(obfs) do + if v and v ~= "" then + o:depends({ + type = "v2ray", + v2ray_protocol = "hysteria2", + obfs_type = v, + flag_obfs = true + }) + end + end +end o = s:option(Flag, "flag_quicparam", translate("Hysterir QUIC parameters")) o:depends("type", "hysteria2") @@ -748,13 +778,13 @@ o:depends("type", "shadowtls") if is_finded("sslocal") then o:value("sslocal", translate("ShadowSocks-rust Version")) end -if is_finded("xray") or is_finded("v2ray") then +if is_finded("xray") then o:value("vmess", translate("Vmess Protocol")) end o.default = "sslocal" o.rmempty = false -o = s:option(Value, "sslocal_password",translate("Shadowsocks password")) +o = s:option(Value, "sslocal_password",translate("Shadowsocks Password")) o:depends({type = "shadowtls", chain_type = "sslocal"}) o.rmempty = true @@ -788,7 +818,7 @@ o:depends("type", "tuic") --Tuic IP o = s:option(Value, "tuic_ip", translate("TUIC Server IP Address")) o.rmempty = true -o.datatype = "ip4addr" +o.datatype = "ipaddr" o.default = "" o:depends("type", "tuic") @@ -969,7 +999,7 @@ o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)")) o.datatype = "integer" o:depends("transport", "ws") -if is_finded("v2ray") then +if is_finded("xray") then -- WS前置数据 o = s:option(Value, "ws_ed", translate("Max Early Data")) o:depends("ws_ed_enable", true) @@ -1210,12 +1240,12 @@ o.default = "0" o.rmempty = true o = s:option(DynamicList, "local_addresses", translate("Local addresses")) -o.datatype = "cidr" +--o.datatype = "cidr" o:depends({type = "v2ray", v2ray_protocol = "wireguard"}) o.rmempty = true o = s:option(DynamicList, "reserved", translate("Reserved bytes(optional)")) -o.description = translate("Decimal numbers separated by \",\" or Base64-encoded strings.") +o.description = translate("Supports decimal numbers separated by \",\" or Base64-encoded strings, with a maximum length of 3 bytes.") o:depends({type = "v2ray", v2ray_protocol = "wireguard"}) o.rmempty = true @@ -1260,12 +1290,18 @@ o:depends("transport", "grpc") o = s:option(Flag, "enable_finalmask", translate("FinalMask")) o.rmempty = true o.default = "0" -o:depends({type = "v2ray", v2ray_protocol = "vless"}) -o:depends({type = "v2ray", v2ray_protocol = "vmess"}) -o:depends({type = "v2ray", v2ray_protocol = "trojan"}) -o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"}) -o:depends({type = "v2ray", v2ray_protocol = "wireguard"}) -o:depends({type = "v2ray", v2ray_protocol = "hysteria2"}) +local protocols = s.fields["v2ray_protocol"].keylist +if protocols and type(protocols) == "table" and #protocols > 0 then + for _, proto in ipairs(protocols) do + if not proto:find("^_") then + if proto == "hysteria2" then + o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false}) + elseif proto ~= "socks" and proto ~= "http" then + o:depends({type = "v2ray", v2ray_protocol = proto}) + end + end + end +end o = s:option(TextValue, "finalmask", " ") o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "
" .. @@ -1362,7 +1398,18 @@ if is_finded("xray") then o = s:option(Flag, "enable_ech", translate("Enable ECH(optional)")) o.rmempty = true o.default = "0" - o:depends({type = "v2ray", tls = true}) + local protocols = s.fields["v2ray_protocol"].keylist + if protocols and type(protocols) == "table" and #protocols > 0 then + for _, proto in ipairs(protocols) do + if not proto:find("^_") then + if proto == "hysteria2" then + o:depends({type = "v2ray", v2ray_protocol = "hysteria2", tls = true, hysteria2_realms = false}) + else + o:depends({type = "v2ray", v2ray_protocol = proto, tls = true}) + end + end + end + end o = s:option(TextValue, "ech_config", translate("ECH Config")) o.description = translate( @@ -1643,100 +1690,41 @@ o.rmempty = true o.default = "0" o:depends("type", "ssr") o:depends("type", "ss") +o:depends("type", "ss-rust") o:depends("type", "trojan") o:depends("type", "hysteria2") o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "xhttp"}) o:depends({type = "v2ray", v2ray_protocol = "hysteria2"}) -o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve")) -o.description = translate( - "
    " .. - "
  • " .. translate("If the node address is a domain name, this DNS will be used for resolution.") .. "
  • " .. - "
  • " .. string.format('%s', translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead.")) .. "
  • " .. - "
" -) -o:value("", translate("Auto")) -o:value("tcp", translate("TCP")) -o:value("udp", translate("UDP")) -o:value("https", translate("DoH")) -o:depends("type", "v2ray") - -o = s:option(Value, "domain_resolver_dns", translate("DNS")) -o.datatype = "or(ipaddr,ipaddrport)" -o:value("114.114.114.114") -o:value("223.5.5.5:53") -o.default = "114.114.114.114" -o:depends("domain_resolver", "tcp") -o:depends("domain_resolver", "udp") - -o = s:option(Value, "domain_resolver_dns_https", translate("DNS")) -o:value("https://120.53.53.53/dns-query", translate("DNSPod")) -o:value("https://223.5.5.5/dns-query", translate("AliDNS")) -o.default = "https://120.53.53.53/dns-query" -o:depends("domain_resolver", "https") - -o = s:option(ListValue, "domain_strategy", translate("Domain Strategy")) -o.description = translate( - "
    " .. - "
  • " .. translate("If is domain name, The requested domain name will be resolved to IP before connect.") .. "
  • " .. - "
  • " .. string.format('%s', translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead.")) .. "
  • " .. - "
" -) -o.default = "" -o:value("", translate("Auto")) -o:value("UseIPv4v6", translate("Prefer IPv4")) -o:value("UseIPv6v4", translate("Prefer IPv6")) -o:value("UseIPv4", translate("IPv4 Only")) -o:value("UseIPv6", translate("IPv6 Only")) -o:depends("type", "v2ray") - -local v2ray_protocols = s.fields["v2ray_protocol"] -if #v2ray_protocols > 0 then - for i, v in ipairs(v2ray_protocols) do - if not v:find("^_") then - s.fields["server"]:depends({ ["v2ray_protocol"] = v }) - s.fields["server_port"]:depends({ ["v2ray_protocol"] = v }) - s.fields["domain_resolver"]:depends({ ["v2ray_protocol"] = v }) - s.fields["domain_strategy"]:depends({ ["v2ray_protocol"] = v }) - - if v ~= "hysteria2" then - s.fields["fast_open"]:depends({ ["v2ray_protocol"] = v }) - s.fields["mptcp"]:depends({ ["v2ray_protocol"] = v }) - end - end - end -end - o = s:option(Flag, "switch_enable", translate("Enable Auto Switch")) o.rmempty = false o.default = "1" -o = s:option(Value, "local_port", translate("Local Port")) -o.datatype = "port" -o.default = 1234 -o.rmempty = false - if is_finded("kcptun-client") then o = s:option(Flag, "kcp_enable", translate("KcpTun Enable")) o.rmempty = true o.default = "0" o:depends("type", "ssr") + o:depends("type", "ss-rust") o:depends("type", "ss") o = s:option(Value, "kcp_port", translate("KcpTun Port")) o.datatype = "portrange" o.default = 4000 o:depends("type", "ssr") + o:depends("type", "ss-rust") o:depends("type", "ss") o = s:option(Value, "kcp_password", translate("KcpTun Password")) o.password = true o:depends("type", "ssr") + o:depends("type", "ss-rust") o:depends("type", "ss") o = s:option(Value, "kcp_param", translate("KcpTun Param")) o.default = "--nocomp" o:depends("type", "ssr") + o:depends("type", "ss-rust") o:depends("type", "ss") end diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua index 7978c57b0..1fd8e775b 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua @@ -4,6 +4,7 @@ local m, s, sec, o local uci = require "luci.model.uci".cursor() +local URL = require "url" -- 获取 LAN IP 地址 function lanip() @@ -31,69 +32,62 @@ end local lan_ip = lanip() local validation = require "luci.cbi.datatypes" +local clash_nodes = {} local function is_finded(e) - return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= "" + return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= "" end -m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"), translate("

Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN etc.

")) +local function clash_display_name(s) + if s.type ~= "clash" or not s.clash_url or s.clash_url == "" then + return nil + end + local ok, parsed = pcall(URL.parse, s.clash_url) + if ok and parsed and parsed.host then + return "[CLASH]:" .. parsed.host + end + return "[CLASH]" +end + +m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"), translate("

Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/CLASH etc.

")) m:section(SimpleSection).template = "shadowsocksr/status" local server_table = {} +local server_order = {} uci:foreach("shadowsocksr", "servers", function(s) - if s.alias then + if s.type == "clash" then + clash_nodes[s[".name"]] = true + end + + if s.type ~= "tun" and s.alias then server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias} - elseif s.server and s.server_port then + elseif s.type ~= "tun" and s.server and s.server_port then server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port} + elseif s.type ~= "tun" then + local display_name = clash_display_name(s) + if display_name then + server_table[s[".name"]] = display_name + end + end + if s.type ~= "tun" and server_table[s[".name"]] then + table.insert(server_order, s[".name"]) end end) -local key_table = {} -for key, _ in pairs(server_table) do - table.insert(key_table, key) -end - -table.sort(key_table) - -- [[ Global Setting ]]-- s = m:section(TypedSection, "global") s.anonymous = true o = s:option(ListValue, "global_server", translate("Main Server")) o:value("nil", translate("Disable")) -for _, key in pairs(key_table) do +for _, key in ipairs(server_order) do o:value(key, server_table[key]) end o.default = "nil" o.rmempty = false -o = s:option(ListValue, "udp_relay_server", translate("Game Mode UDP Server")) -o:value("", translate("Disable")) -o:value("same", translate("Same as Global Server")) -for _, key in pairs(key_table) do - o:value(key, server_table[key]) -end - -if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') == '1' then - o = s:option(ListValue, "netflix_server", translate("Netflix Node")) - o:value("nil", translate("Disable")) - o:value("same", translate("Same as Global Server")) - for _, key in pairs(key_table) do - o:value(key, server_table[key]) - end - o.default = "nil" - o.rmempty = false - - o = s:option(Flag, "netflix_proxy", translate("External Proxy Mode")) - o.rmempty = false - o.description = translate("Forward Netflix Proxy through Main Proxy") - o.default = "0" -end - --- [[ Use nftables/iptables ]]-- -o = s:option(ListValue, "prefer_nft", translate("Prefer firewall tools")) -o.default = "1" -o:value("0", "Iptables") -o:value("1", "Nftables") +o = s:option(DummyValue, "_clash_panel", translate("Clash Panel")) +o.template = "shadowsocksr/clash_main_panel" +o.clash_nodes = clash_nodes o = s:option(ListValue, "threads", translate("Multi Threads Option")) o:value("0", translate("Auto Threads")) @@ -112,7 +106,6 @@ o = s:option(ListValue, "run_mode", translate("Running Mode")) o:value("gfw", translate("GFW List Mode")) o:value("router", translate("IP Route Mode")) o:value("all", translate("Global Mode")) -o:value("oversea", translate("Oversea Mode")) o.default = gfw o = s:option(ListValue, "dports", translate("Proxy Ports")) @@ -128,21 +121,13 @@ o = s:option(ListValue, "pdnsd_enable", translate("Resolve Dns Mode")) if is_finded("dns2tcp") then o:value("1", translate("Use DNS2TCP query")) end -if is_finded("dns2socks") then - o:value("2", translate("Use DNS2SOCKS query and cache")) -end -if is_finded("dns2socks-rust") then - o:value("3", translate("Use DNS2SOCKS-RUST query and cache")) -end if is_finded("mosdns") then - o:value("4", translate("Use MOSDNS query (Not Support Oversea Mode)")) -end -if is_finded("dnsproxy") then - o:value("5", translate("Use DNSPROXY query and cache")) + o:value("4", translate("Use MosDNS query")) end if is_finded("chinadns-ng") then o:value("6", translate("Use ChinaDNS-NG query and cache")) end +o:value("7", translate("Prefer module built-in DNS")) o:value("0", translate("Use Local DNS Service listen port 5335")) o.default = 1 @@ -158,11 +143,8 @@ o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)")) o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)")) o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)")) o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)")) -o:value("114.114.114.114:53", translate("Oversea Mode DNS-1 (114.114.114.114)")) -o:value("114.114.115.115:53", translate("Oversea Mode DNS-2 (114.114.115.115)")) o:depends("pdnsd_enable", "1") -o:depends("pdnsd_enable", "2") -o:depends("pdnsd_enable", "3") +o:depends("pdnsd_enable", "7") o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") o.datatype = "ip4addrport" o.default = "8.8.4.4:53" @@ -176,61 +158,15 @@ o:value("tcp://4.2.2.3:53,tcp://4.2.2.4:53", translate("Level 3 Public DNS-3 (4. o:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS")) o:depends("pdnsd_enable", "4") o.description = translate("Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT (tcp://8.8.8.8 or tls://dns.google:853)") +o.default = "tcp://8.8.4.4:53,tcp://8.8.8.8:53" -o = s:option(Flag, "mosdns_ipv6", translate("Disable IPv6 in MOSDNS query mode")) +o = s:option(Flag, "filter_aaaa", translate("Disable IPv6 for Overseas FQDN")) +o:depends("pdnsd_enable", "1") o:depends("pdnsd_enable", "4") +o:depends("pdnsd_enable", "7") o.rmempty = false o.default = "1" -if is_finded("dnsproxy") then - o = s:option(ListValue, "parse_method", translate("Select DNS parse Mode")) - o.description = translate( - "" - ) - o:value("single_dns", translate("Set Single DNS")) - o:value("parse_file", translate("Use DNS List File")) - o:depends("pdnsd_enable", "5") - o.rmempty = true - o.default = "single_dns" - - o = s:option(Value, "dnsproxy_tunnel_forward", translate("Anti-pollution DNS Server")) - o:value("sdns://AgUAAAAAAAAABzguOC40LjQgsKKKE4EwvtIbNjGjagI2607EdKSVHowYZtyvD9iPrkkHOC44LjQuNAovZG5zLXF1ZXJ5", translate("Google DNSCrypt SDNS")) - o:value("sdns://AgcAAAAAAAAAACC2vD25TAYM7EnyCH8Xw1-0g5OccnTsGH9vQUUH0njRtAxkbnMudHduaWMudHcKL2Rucy1xdWVyeQ", translate("TWNIC-101 DNSCrypt SDNS")) - o:value("sdns://AgcAAAAAAAAADzE4NS4yMjIuMjIyLjIyMiAOp5Svj-oV-Fz-65-8H2VKHLKJ0egmfEgrdPeAQlUFFA8xODUuMjIyLjIyMi4yMjIKL2Rucy1xdWVyeQ", translate("dns.sb DNSCrypt SDNS")) - o:value("sdns://AgMAAAAAAAAADTE0OS4xMTIuMTEyLjkgsBkgdEu7dsmrBT4B4Ht-BQ5HPSD3n3vqQ1-v5DydJC8SZG5zOS5xdWFkOS5uZXQ6NDQzCi9kbnMtcXVlcnk", translate("Quad9 DNSCrypt SDNS")) - o:value("sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", translate("AdGuard DNSCrypt SDNS")) - o:value("sdns://AgcAAAAAAAAABzEuMC4wLjGgENk8mGSlIfMGXMOlIlCcKvq7AVgcrZxtjon911-ep0cg63Ul-I8NlFj4GplQGb_TTLiczclX57DvMV8Q-JdjgRgSZG5zLmNsb3VkZmxhcmUuY29tCi9kbnMtcXVlcnk", translate("Cloudflare DNSCrypt SDNS")) - o:value("sdns://AgcAAAAAAAAADjEwNC4xNi4yNDkuMjQ5ABJjbG91ZGZsYXJlLWRucy5jb20KL2Rucy1xdWVyeQ", translate("cloudflare-dns.com DNSCrypt SDNS")) - o:depends("parse_method", "single_dns") - o.description = translate("Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-query and other format).") - - o = s:option(ListValue, "upstreams_logic_mode", translate("Defines the upstreams logic mode")) - o.description = translate( - "
    " .. - "
  • " .. translate("Defines the upstreams logic mode, possible values: load_balance, parallel, fastest_addr (default: load_balance).") .. "
  • " .. - "
  • " .. translate("When two or more DNS servers are deployed, enable this function.") .. "
  • " .. - "
" - ) - o:value("load_balance", translate("load_balance")) - o:value("parallel", translate("parallel")) - o:value("fastest_addr", translate("fastest_addr")) - o:depends("parse_method", "parse_file") - o.rmempty = true - o.default = "load_balance" - - o = s:option(Flag, "dnsproxy_ipv6", translate("Disable IPv6 query mode")) - o.description = translate("When disabled, all AAAA requests are not resolved.") - o:depends("parse_method", "single_dns") - o:depends("parse_method", "parse_file") - o.rmempty = false - o.default = "1" -end - if is_finded("chinadns-ng") then o = s:option(Value, "chinadns_ng_tunnel_forward", translate("Anti-pollution DNS Server")) o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)")) @@ -245,6 +181,7 @@ if is_finded("chinadns-ng") then o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)")) o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)")) o:depends("pdnsd_enable", "6") + o.default = "8.8.4.4:53" o.description = translate( "
    " .. "
  • " .. translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") .. "
  • " .. @@ -270,11 +207,8 @@ if is_finded("chinadns-ng") then o:value("101.226.4.6:53", translate("360 Security DNS (China Telecom) (101.226.4.6)")) o:value("123.125.81.6:53", translate("360 Security DNS (China Unicom) (123.125.81.6)")) o:value("1.2.4.8:53", translate("CNNIC SDNS (1.2.4.8)")) - o:depends({pdnsd_enable = "1", run_mode = "router"}) - o:depends({pdnsd_enable = "2", run_mode = "router"}) - o:depends({pdnsd_enable = "3", run_mode = "router"}) - o:depends({pdnsd_enable = "5", run_mode = "router"}) o:depends({pdnsd_enable = "6", run_mode = "router"}) + o.default = "wan" o.description = translate("Custom DNS Server format as IP:PORT (default: disabled)") o.validate = function(self, value, section) if (section and value) then @@ -293,5 +227,7 @@ if is_finded("chinadns-ng") then end end -m:section(SimpleSection).template = "shadowsocksr/status_bottom" +m:append(Template("shadowsocksr/client_dns_defaults")) +m:append(Template("shadowsocksr/status_bottom")) + return m diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua new file mode 100644 index 000000000..8d670cd79 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua @@ -0,0 +1,30 @@ +local m, s, o + +m = SimpleForm( + "component_update", + translate("Component Update"), + translate("Check installed component versions and upgrade them online from the upstream release page.") +) +m.reset = false +m.submit = false + +s = m:section(SimpleSection) + +o = m:field(ListValue, "component_mirror", translate("Mirror URL")) +o:value("direct", translate("GitHub Direct")) +o:value("ghproxy", "mirror.ghproxy.com") +o:value("ghproxy_cc", "ghproxy.cc") +o:value("ghfast", "ghfast.top") +o:value("jsdelivr", "cdn.jsdelivr.net") +o.rmempty = false +o.cfgvalue = function(self) + return m.uci:get_first("shadowsocksr", "global", "component_mirror") or "direct" +end +o.write = function(self, section, value) + m.uci:set("shadowsocksr", "@global[0]", "component_mirror", value) + m.uci:commit("shadowsocksr") +end + +s.template = "shadowsocksr/component" + +return m diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua index 4d4367a7d..0d92887ff 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua @@ -3,10 +3,6 @@ require "nixio.fs" require "luci.sys" local m, s, o -local function is_finded(e) - return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= "" -end - m = Map("shadowsocksr") s = m:section(TypedSection, "access_control") @@ -129,40 +125,6 @@ o.remove = function(self, section, value) nixio.fs.writefile(denydomainconf, "") end -s:tab("netflix", translate("Netflix Domain List")) -local netflixconf = "/etc/ssrplus/netflix.list" -o = s:taboption("netflix", TextValue, "netflixconf") -o.rows = 13 -o.wrap = "off" -o.rmempty = true -o.cfgvalue = function(self, section) - return nixio.fs.readfile(netflixconf) or " " -end -o.write = function(self, section, value) - nixio.fs.writefile(netflixconf, value:gsub("\r\n", "\n")) -end -o.remove = function(self, section, value) - nixio.fs.writefile(netflixconf, "") -end - -if is_finded("dnsproxy") then - s:tab("dnsproxy", translate("Dnsproxy Parse List")) - local dnsproxyconf = "/etc/ssrplus/dnsproxy_dns.list" - o = s:taboption("dnsproxy", TextValue, "dnsproxyconf", "", "" .. translate("Specifically for edit dnsproxy DNS parse files.") .. "") - o.rows = 13 - o.wrap = "off" - o.rmempty = true - o.cfgvalue = function(self, section) - return nixio.fs.readfile(dnsproxyconf) or " " - end - o.write = function(self, section, value) - nixio.fs.writefile(dnsproxyconf, value:gsub("\r\n", "\n")) - end - o.remove = function(self, section, value) - nixio.fs.writefile(dnsproxyconf, "") - end -end - if luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then m.apply_on_parse = true function m.on_apply(self) @@ -170,4 +132,6 @@ if luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then end end +m:append(Template("shadowsocksr/control_layout")) + return m diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/log.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/log.lua index c7af14798..6aa8328b4 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/log.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/log.lua @@ -50,6 +50,7 @@ luci.http.setfilehandler(function(meta, chunk, eof) luci.sys.call("rm -rf " .. temp_dir) nixio.fs.remove(file_path) fd = nixio.open(file_path, "w") + luci.sys.call("ls /tmp/sub_md5_* >/dev/null 2>&1 && rm -f /tmp/sub_md5_*") luci.sys.call("echo '' > /var/log/ssrplus.log") end @@ -96,6 +97,7 @@ luci.http.setfilehandler(function(meta, chunk, eof) -- 清理临时文件 luci.sys.call("rm -rf " .. temp_dir) nixio.fs.remove(file_path) + luci.sys.call("ls /tmp/sub_md5_* >/dev/null 2>&1 && rm -f /tmp/sub_md5_*") end end) diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua index 145c400f8..2fdf777d4 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua @@ -7,68 +7,39 @@ require "nixio.fs" local m, s, o local sid = arg[1] +-- ¼ÓÃܷ½ʽ£¨SS/SSRÓã© local encrypt_methods = { - "rc4-md5", - "rc4-md5-6", - "rc4", - "table", - "aes-128-cfb", - "aes-192-cfb", - "aes-256-cfb", - "aes-128-ctr", - "aes-192-ctr", - "aes-256-ctr", - "bf-cfb", - "camellia-128-cfb", - "camellia-192-cfb", - "camellia-256-cfb", - "cast5-cfb", - "des-cfb", - "idea-cfb", - "rc2-cfb", - "seed-cfb", - "salsa20", - "chacha20", - "chacha20-ietf" + "rc4-md5", "rc4-md5-6", "rc4", "table", + "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", + "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", + "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", + "cast5-cfb", "des-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", + "salsa20", "chacha20", "chacha20-ietf" } local encrypt_methods_ss = { - -- aead - "aes-128-gcm", - "aes-192-gcm", - "aes-256-gcm", - "chacha20-ietf-poly1305", - "xchacha20-ietf-poly1305", - -- aead 2022 - "2022-blake3-aes-128-gcm", - "2022-blake3-aes-256-gcm", - "2022-blake3-chacha20-poly1305" - --[[ stream - "table", - "rc4", - "rc4-md5", - "aes-128-cfb", - "aes-192-cfb", - "aes-256-cfb", - "aes-128-ctr", - "aes-192-ctr", - "aes-256-ctr", - "bf-cfb", - "camellia-128-cfb", - "camellia-192-cfb", - "camellia-256-cfb", - "salsa20", - "chacha20", - "chacha20-ietf" ]] + "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", + "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", + "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" +} + +-- Shadowsocks ¼ÓÃܷ½·¨£¨Xray °棩 +local v_ss_encrypt_method_list = { + "aes-128-cfb", "aes-256-cfb", "aes-128-gcm", "aes-256-gcm", + "chacha20", "chacha20-ietf", "chacha20-poly1305", "chacha20-ietf-poly1305" +} + +-- αװÀàÐͣ¨ÓÃÓÚ mKCP/QUIC£© +local header_type_list = { + "none", "srtp", "utp", "wechat-video", "dtls", "wireguard" } local protocol = {"origin"} - obfs = {"plain", "http_simple", "http_post"} m = Map("shadowsocksr", translate("Edit ShadowSocksR Server")) - m.redirect = luci.dispatcher.build_url("admin/services/shadowsocksr/server") + if m.uci:get("shadowsocksr", sid) ~= "server_config" then luci.http.redirect(m.redirect) return @@ -79,18 +50,26 @@ s = m:section(NamedSection, sid, "server_config") s.anonymous = true s.addremove = false +-- ========== »ù±¾ÉèÖà ========== o = s:option(Flag, "enable", translate("Enable")) o.default = 1 o.rmempty = false o = s:option(ListValue, "type", translate("Server Type")) o:value("socks5", translate("Socks5")) -if nixio.fs.access("/usr/bin/ssserver") or nixio.fs.access("/usr/bin/ss-server") then +if nixio.fs.access("/usr/bin/mihomo") or nixio.fs.access("/usr/libexec/mihomo") or nixio.fs.access("/usr/bin/ssserver") or nixio.fs.access("/usr/libexec/ssserver") then o:value("ss", translate("ShadowSocks")) end if nixio.fs.access("/usr/bin/ssr-server") then o:value("ssr", translate("ShadowsocksR")) end +-- Xray ЭÒé֧³Ö +if nixio.fs.access("/usr/bin/xray") or nixio.fs.access("/usr/libexec/xray") then + o:value("vmess", "VMess (Xray)") + o:value("vless", "VLESS (Xray)") + o:value("trojan", "Trojan (Xray)") + o:value("shadowsocks", "Shadowsocks (Xray)") +end o.default = "socks5" o = s:option(Value, "server_port", translate("Server Port")) @@ -114,32 +93,30 @@ o:depends("type", "socks5") o = s:option(Value, "password", translate("Password")) o.password = true o.rmempty = false +o:depends("type", "socks5") +o:depends("type", "ss") +o:depends("type", "ssr") +o:depends("type", "trojan") +o:depends("type", "shadowsocks") +-- ========== SS/SSR ЭÒéÌØÓÐ ========== o = s:option(ListValue, "encrypt_method", translate("Encrypt Method")) -for _, v in ipairs(encrypt_methods) do - o:value(v) -end +for _, v in ipairs(encrypt_methods) do o:value(v) end o.rmempty = false o:depends("type", "ssr") o = s:option(ListValue, "encrypt_method_ss", translate("Encrypt Method")) -for _, v in ipairs(encrypt_methods_ss) do - o:value(v) -end +for _, v in ipairs(encrypt_methods_ss) do o:value(v) end o.rmempty = false o:depends("type", "ss") o = s:option(ListValue, "protocol", translate("Protocol")) -for _, v in ipairs(protocol) do - o:value(v) -end +for _, v in ipairs(protocol) do o:value(v) end o.rmempty = false o:depends("type", "ssr") o = s:option(ListValue, "obfs", translate("Obfs")) -for _, v in ipairs(obfs) do - o:value(v) -end +for _, v in ipairs(obfs) do o:value(v) end o.rmempty = false o:depends("type", "ssr") @@ -151,4 +128,269 @@ o.rmempty = false o:depends("type", "ss") o:depends("type", "ssr") -return m +-- ========== Xray ЭÒéͨÓÃ×ֶÎ ========== +-- Óû§±¸ע +o = s:option(Value, "remarks", translate("Remarks")) +o.default = translate("Remarks") +o.rmempty = true +o:depends("type", "vmess") +o:depends("type", "vless") +o:depends("type", "trojan") +o:depends("type", "shadowsocks") + +-- Óû§µȼ¶ +o = s:option(Value, "level", translate("User Level")) +o.datatype = "uinteger" +o.default = 1 +o.rmempty = true +o:depends("type", "vmess") +o:depends("type", "vless") +o:depends("type", "shadowsocks") +o:depends("type", "trojan") + +-- ========== Xray ЭÒéÈÏ֤×ֶÎ ========== +-- UUID (ÓÃÓÚ vmess/vless) +o = s:option(Value, "uuid", translate("UUID")) +o.description = translate("Required for VMess/VLESS. Generate with: uuidgen") +o.rmempty = true +o:depends("type", "vmess") +o:depends("type", "vless") + +-- Trojan ÃÜÂë +o = s:option(Value, "trojan_password", translate("Trojan Password")) +o.password = true +o.rmempty = true +o:depends("type", "trojan") + +-- Shadowsocks ÃÜÂëºͼÓÃܣ¨Xray ÄÚÖã© +o = s:option(Value, "ss_password", translate("Shadowsocks Password")) +o.password = true +o.rmempty = true +o:depends("type", "shadowsocks") + +o = s:option(ListValue, "ss_method", translate("Encrypt Method")) +for _, v in ipairs(v_ss_encrypt_method_list) do o:value(v) end +o.default = "chacha20-ietf-poly1305" +o.rmempty = true +o:depends("type", "shadowsocks") + +o = s:option(ListValue, "ss_network", translate("Transport")) +o:value("tcp", "TCP") +o:value("udp", "UDP") +o:value("tcp,udp", "TCP,UDP") +o.default = "tcp,udp" +o.rmempty = true +o:depends("type", "shadowsocks") + +-- VMess alterId +o = s:option(Value, "alter_id", translate("Alter ID")) +o.datatype = "uinteger" +o.default = 0 +o.rmempty = true +o:depends("type", "vmess") + +-- VLESS decryption +o = s:option(Value, "decryption", translate("Decryption")) +o.default = "none" +o.rmempty = true +o:depends("type", "vless") + +-- ========== TLS / XTLS ÉèÖà ========== +o = s:option(Flag, "tls", translate("TLS")) +o.default = 0 +o.rmempty = true +o:depends("type", "vmess") +o:depends("type", "vless") +o:depends("type", "trojan") +o:depends("type", "shadowsocks") + +o = s:option(Flag, "xtls", translate("XTLS")) +o.default = 0 +o.rmempty = true +o:depends({ type = "vless", tls = "1" }) + +o = s:option(ListValue, "flow", translate("Flow")) +o:value("xtls-rprx-origin", "xtls-rprx-origin") +o:value("xtls-rprx-origin-udp443", "xtls-rprx-origin-udp443") +o:value("xtls-rprx-direct", "xtls-rprx-direct") +o:value("xtls-rprx-direct-udp443", "xtls-rprx-direct-udp443") +o:value("xtls-rprx-splice", "xtls-rprx-splice") +o:value("xtls-rprx-splice-udp443", "xtls-rprx-splice-udp443") +o.default = "xtls-rprx-direct" +o.rmempty = true +o:depends("xtls", "1") + +o = s:option(Value, "tls_serverName", translate("Server Name (SNI)")) +o.rmempty = true +o:depends("tls", "1") + +o = s:option(Value, "tls_certificateFile", translate("Certificate File Path")) +o.description = translate("e.g.: /etc/ssl/fullchain.pem") +o.rmempty = true +o:depends("tls", "1") + +o = s:option(Value, "tls_keyFile", translate("Private Key File Path")) +o.description = translate("e.g.: /etc/ssl/private.key") +o.rmempty = true +o:depends("tls", "1") + +-- ========== ´«Êä²ãÉèÖà (Transport) ========== +o = s:option(ListValue, "transport", translate("Transport Protocol")) +o:value("tcp", "TCP") +o:value("mkcp", "mKCP") +o:value("ws", "WebSocket") +o:value("h2", "HTTP/2") +o:value("ds", "DomainSocket") +o:value("quic", "QUIC") +o.default = "tcp" +o.rmempty = true +o:depends("type", "vmess") +o:depends("type", "vless") +o:depends("type", "trojan") + +-- ----- WebSocket ÉèÖà ----- +o = s:option(Value, "ws_host", translate("WebSocket Host")) +o.rmempty = true +o:depends("transport", "ws") +o:depends("type", "vmess") +o:depends("type", "vless") +o:depends("type", "trojan") + +o = s:option(Value, "ws_path", translate("WebSocket Path")) +o.default = "/" +o.rmempty = true +o:depends("transport", "ws") +o:depends("type", "vmess") +o:depends("type", "vless") +o:depends("type", "trojan") + +-- ----- HTTP/2 ÉèÖà ----- +o = s:option(Value, "h2_host", translate("HTTP/2 Host")) +o.rmempty = true +o:depends("transport", "h2") +o:depends("type", "vmess") +o:depends("type", "vless") + +o = s:option(Value, "h2_path", translate("HTTP/2 Path")) +o.default = "/" +o.rmempty = true +o:depends("transport", "h2") +o:depends("type", "vmess") +o:depends("type", "vless") + +-- ----- TCP αװÉèÖà ----- +o = s:option(ListValue, "tcp_guise", translate("TCP Camouflage Type")) +o:value("none", "none") +o:value("http", "http") +o.default = "none" +o.rmempty = true +o:depends("transport", "tcp") +o:depends("type", "vmess") +o:depends("type", "vless") + +o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host")) +o.rmempty = true +o:depends("tcp_guise", "http") + +o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path")) +o.rmempty = true +o:depends("tcp_guise", "http") + +-- ----- mKCP ÉèÖà ----- +o = s:option(ListValue, "mkcp_guise", translate("mKCP Camouflage Type")) +for _, v in ipairs(header_type_list) do o:value(v) end +o.default = "none" +o.rmempty = true +o:depends("transport", "mkcp") + +o = s:option(Value, "mkcp_mtu", translate("KCP MTU")) +o.datatype = "uinteger" +o.default = 1350 +o.rmempty = true +o:depends("transport", "mkcp") + +o = s:option(Value, "mkcp_tti", translate("KCP TTI")) +o.datatype = "uinteger" +o.default = 20 +o.rmempty = true +o:depends("transport", "mkcp") + +o = s:option(Value, "mkcp_uplinkCapacity", translate("KCP Uplink Capacity")) +o.datatype = "uinteger" +o.default = 5 +o.rmempty = true +o:depends("transport", "mkcp") + +o = s:option(Value, "mkcp_downlinkCapacity", translate("KCP Downlink Capacity")) +o.datatype = "uinteger" +o.default = 20 +o.rmempty = true +o:depends("transport", "mkcp") + +o = s:option(Flag, "mkcp_congestion", translate("KCP Congestion Control")) +o.default = 0 +o.rmempty = true +o:depends("transport", "mkcp") + +o = s:option(Value, "mkcp_readBufferSize", translate("KCP Read Buffer Size")) +o.datatype = "uinteger" +o.default = 1 +o.rmempty = true +o:depends("transport", "mkcp") + +o = s:option(Value, "mkcp_writeBufferSize", translate("KCP Write Buffer Size")) +o.datatype = "uinteger" +o.default = 1 +o.rmempty = true +o:depends("transport", "mkcp") + +o = s:option(Value, "mkcp_seed", translate("KCP Seed")) +o.datatype = "uinteger" +o.rmempty = true +o:depends("transport", "mkcp") + +-- ----- DomainSocket ÉèÖà ----- +o = s:option(Value, "ds_path", translate("DomainSocket Path")) +o.description = translate("A legal file path. This file must not exist before running.") +o.rmempty = true +o:depends("transport", "ds") + +-- ----- QUIC ÉèÖà ----- +o = s:option(ListValue, "quic_security", translate("QUIC Security")) +o:value("none", "none") +o:value("aes-128-gcm", "aes-128-gcm") +o:value("chacha20-poly1305", "chacha20-poly1305") +o.default = "none" +o.rmempty = true +o:depends("transport", "quic") + +o = s:option(Value, "quic_key", translate("QUIC Key")) +o.rmempty = true +o:depends("transport", "quic") + +o = s:option(ListValue, "quic_guise", translate("QUIC Camouflage Type")) +for _, v in ipairs(header_type_list) do o:value(v) end +o.default = "none" +o.rmempty = true +o:depends("transport", "quic") + +-- ========== ·ÃÎʿØÖÆ ========== +o = s:option(Flag, "bind_local", translate("Bind Local Only")) +o.description = translate("When selected, it can only be accessed locally. Recommended when using reverse proxies.") +o.default = 0 +o.rmempty = true +o:depends("type", "vmess") +o:depends("type", "vless") +o:depends("type", "trojan") +o:depends("type", "shadowsocks") + +o = s:option(Flag, "accept_lan", translate("Accept LAN Access")) +o.description = translate("When selected, it can be accessed from LAN. This may not be safe!") +o.default = 0 +o.rmempty = true +o:depends("type", "vmess") +o:depends("type", "vless") +o:depends("type", "trojan") +o:depends("type", "shadowsocks") + +return m \ No newline at end of file diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua index db3160ab4..c2ac03c60 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua @@ -105,8 +105,13 @@ end o.rmempty = false o = sec:option(DummyValue, "type", translate("Server Type")) -function o.cfgvalue(...) - return Value.cfgvalue(...) or "ss" +function o.cfgvalue(self, section) + local val = Value.cfgvalue(self, section) or "ss" + if val == "vmess" then return "VMess (Xray)" + elseif val == "vless" then return "VLESS (Xray)" + elseif val == "trojan" then return "Trojan (Xray)" + elseif val == "shadowsocks" then return "Shadowsocks (Xray)" + else return val end end o = sec:option(DummyValue, "server_port", translate("Server Port")) @@ -135,4 +140,4 @@ function o.cfgvalue(...) return Value.cfgvalue(...) or "-" end -return m +return m \ No newline at end of file diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua index ac087f474..6e5e01e3e 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua @@ -1,36 +1,35 @@ -- Licensed to the public under the GNU General Public License v3. require "luci.http" require "luci.sys" +require "luci.util" require "nixio.fs" require "luci.dispatcher" require "luci.model.uci" +local nixio = require "nixio" +local json = require "luci.jsonc" local cbi = require "luci.cbi" local uci = require "luci.model.uci".cursor() +local URL = require "url" local m, s, o, node local server_count = 0 +local server_cache = {} +local server_sections = {} +local detect_cache = {} +local CLASH_YAML_DIR = "/etc/ssrplus/clash" +local CLASH_YAML_HELPER = "/usr/share/shadowsocksr/clash_yaml.lua" +local DEFAULT_SERVER_PAGE_SIZE = 50 +local SERVER_PAGE_SIZE_OPTIONS = {25, 50, 100, 200, 0} --- 确保正确判断程序是否存在 local function is_finded(e) - return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= "" + return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= "" end local function is_js_luci() return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 end -local function url(...) - local url = string.format("admin/services/%s", "shadowsocksr") - local args = { ... } - for i, v in ipairs(args) do - if v and v ~= "" then - url = url .. "/" .. v - end - end - return require "luci.dispatcher".build_url(url) -end - --- 默认的保存并应用行为 +-- 保存并应用行为 local function apply_redirect(m) local tmp_uci_file = "/etc/config/" .. "shadowsocksr" .. "_redirect" if m.redirect and m.redirect ~= "" then @@ -65,50 +64,341 @@ local function set_apply_on_parse(map) local old = map.on_after_save map.on_after_save = function(self) if old then old(self) end - map:set("@global[0]", "timestamp", os.time()) + -- map:set("@global[0]", "timestamp", os.time()) end end end -local has_ss_rust = is_finded("sslocal") or is_finded("ssserver") -local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local") -local has_trojan = is_finded("trojan") -local has_xray = is_finded("xray") -local has_hysteria2 = is_finded("hysteria") - -local ss_type_list = {} -local tj_type_list = {} -local hy2_type_list = {} - -if has_hysteria2 then - table.insert(hy2_type_list, { id = "hysteria2", name = translate("Hysteria2") }) -end -if has_xray then - table.insert(hy2_type_list, { id = "v2ray", name = translate("Xray (Hysteria2)") }) +local function trim(text) + if not text or text == "" then + return "" + end + return (text:gsub("^%s*(.-)%s*$", "%1")) end -if has_trojan then - table.insert(tj_type_list, { id = "trojan", name = translate("Trojan") }) -end -if has_xray then - table.insert(tj_type_list, { id = "v2ray", name = translate("Xray (Trojan)") }) +local function parse_nonnegative_int(value) + local number = tonumber(value) + if not number then + return nil + end + number = math.floor(number) + if number < 0 then + return nil + end + return number end -if has_ss_rust then - table.insert(ss_type_list, { id = "ss-rust", name = translate("ShadowSocks-rust Version") }) +local function in_list(list, target) + for _, item in ipairs(list) do + if item == target then + return true + end + end + return false end -if has_ss_libev then - table.insert(ss_type_list, { id = "ss-libev", name = translate("ShadowSocks-libev Version") }) + +local function upload_alias(filename) + local stem = tostring(filename or ""):gsub("\\", "/"):match("([^/]+)$") or "custom" + stem = stem:gsub("%.%w+$", "") + stem = trim(stem):gsub("[%c\r\n]+", " "):gsub("%s+", " ") + if stem == "" then + stem = "custom" + end + return "Clash_" .. stem end -if has_xray then - table.insert(ss_type_list, { id = "v2ray", name = translate("Xray (ShadowSocks)") }) + +local function hash_file(path) + local cmd = "md5sum " .. luci.util.shellquote(path) .. " 2>/dev/null | awk '{print $1}'" + return trim(luci.sys.exec(cmd)) end +local function preprocess_clash_yaml(input_path, output_path) + local cmd = string.format( + "/usr/bin/lua %s prepare %s %s >/dev/null 2>&1", + luci.util.shellquote(CLASH_YAML_HELPER), + luci.util.shellquote(input_path), + luci.util.shellquote(output_path) + ) + return luci.sys.call(cmd) == 0 +end + +local function clash_path_in_use(path, exclude_sid) + local in_use = false + + uci:foreach("shadowsocksr", "servers", function(section) + if section[".name"] ~= exclude_sid and section.clash_path == path then + in_use = true + return false + end + end) + + return in_use +end + +local function cleanup_old_clash_path(old_path, new_path, sid) + if not old_path or old_path == "" or old_path == new_path then + return + end + if old_path:sub(1, #CLASH_YAML_DIR + 1) ~= CLASH_YAML_DIR .. "/" then + return + end + if clash_path_in_use(old_path, sid) then + return + end + nixio.fs.remove(old_path) +end + +local function find_uploaded_clash_section(upload_name, final_path) + local sid + + uci:foreach("shadowsocksr", "servers", function(section) + if section.type ~= "clash" or section.yaml_upload ~= "1" then + return + end + if upload_name ~= "" and section.yaml_upload_name == upload_name then + sid = section[".name"] + return false + end + if final_path ~= "" and section.clash_path == final_path then + sid = section[".name"] + return false + end + end) + + return sid +end + +local function save_uploaded_clash_node(upload_name, final_path) + local sid = find_uploaded_clash_section(upload_name, final_path) + local old_path + local alias + + if not sid then + sid = uci:add("shadowsocksr", "servers") + end + if not sid then + return nil + end + + old_path = uci:get("shadowsocksr", sid, "clash_path") + alias = uci:get("shadowsocksr", sid, "alias") + if not alias or alias == "" then + alias = upload_alias(upload_name) + end + + uci:set("shadowsocksr", sid, "type", "clash") + uci:set("shadowsocksr", sid, "alias", alias) + uci:set("shadowsocksr", sid, "server", "127.0.0.1") + uci:set("shadowsocksr", sid, "server_port", "0") + uci:delete("shadowsocksr", sid, "clash_url") + uci:set("shadowsocksr", sid, "clash_path", final_path) + uci:set("shadowsocksr", sid, "clash_user_agent", uci:get("shadowsocksr", sid, "clash_user_agent") or "clash") + if not uci:get("shadowsocksr", sid, "switch_enable") then + uci:set("shadowsocksr", sid, "switch_enable", uci:get_first("shadowsocksr", "server_subscribe", "switch", "1") or "1") + end + uci:set("shadowsocksr", sid, "yaml_upload", "1") + uci:set("shadowsocksr", sid, "yaml_upload_name", upload_name) + uci:save("shadowsocksr") + uci:commit("shadowsocksr") + + cleanup_old_clash_path(old_path, final_path, sid) + luci.sys.call(string.format("/etc/init.d/shadowsocksr clash_cache %s >/dev/null 2>&1 &", luci.util.shellquote(sid))) + return sid, alias +end + +local has_mihomo = is_finded("mihomo") +local upload_fd +local upload_tmp_path +local upload_filename +local upload_message +local upload_errmessage + +if has_mihomo then + luci.http.setfilehandler(function(meta, chunk, eof) + if not meta or meta.name ~= "clash_yaml_file" then + return + end + + if not upload_fd then + if not meta.file or meta.file == "" then + return + end + upload_filename = tostring(meta.file):gsub("[\r\n]", "") + upload_tmp_path = string.format("/tmp/ssrplus-clash-upload-%d-%d.yaml", nixio.getpid(), os.time()) + upload_fd = nixio.open(upload_tmp_path, "w") + if not upload_fd then + upload_errmessage = translate("Failed to create temporary YAML upload file.") + upload_tmp_path = nil + upload_filename = nil + return + end + end + + if chunk and upload_fd then + upload_fd:write(chunk) + end + + if eof and upload_fd then + upload_fd:close() + upload_fd = nil + end + end) + + if luci.http.formvalue("upload_clash_yaml") then + if not upload_tmp_path or not upload_filename or not nixio.fs.access(upload_tmp_path) then + upload_errmessage = upload_errmessage or translate("No custom YAML file was selected.") + else + local hash + local final_path + local tmp_output = string.format("%s/.upload-%d-%d.yaml", CLASH_YAML_DIR, nixio.getpid(), os.time()) + local sid + local alias + + nixio.fs.mkdirr(CLASH_YAML_DIR) + nixio.fs.remove(tmp_output) + if preprocess_clash_yaml(upload_tmp_path, tmp_output) then + hash = hash_file(tmp_output) + if hash == "" then + nixio.fs.remove(tmp_output) + upload_errmessage = translate("Uploaded YAML validation or preprocessing failed.") + else + final_path = string.format("%s/%s.yaml", CLASH_YAML_DIR, hash) + luci.sys.call(string.format("mv -f %s %s", luci.util.shellquote(tmp_output), luci.util.shellquote(final_path))) + sid, alias = save_uploaded_clash_node(upload_filename, final_path) + if sid then + upload_message = string.format(translate("Custom YAML imported successfully: %s"), alias or sid) + else + upload_errmessage = translate("Uploaded YAML validation or preprocessing failed.") + end + end + else + nixio.fs.remove(tmp_output) + upload_errmessage = translate("Uploaded YAML validation or preprocessing failed.") + end + end + + if upload_tmp_path then + nixio.fs.remove(upload_tmp_path) + end + end +end + +local function preserve_when_hidden(opt, controller, enabled_value) + local original_parse = opt.parse + + opt.parse = function(self, section, novld) + local current = self.map:get(section, controller) + if current == nil then + current = self.map:formvalue("cbid." .. self.map.config .. "." .. section .. "." .. controller) + end + if tostring(current or "") ~= tostring(enabled_value) then + return + end + return original_parse(self, section, novld) + end +end + +local function migrate_legacy_subscribe_urls() + local subscribe_sid = uci:get_first("shadowsocksr", "server_subscribe") + if not subscribe_sid then + return + end + + local legacy_urls = uci:get_list("shadowsocksr", subscribe_sid, "subscribe_url") or {} + if #legacy_urls == 0 then + return + end + + local has_items = false + uci:foreach("shadowsocksr", "server_subscribe_item", function() + has_items = true + return false + end) + if has_items then + return + end + + for index, url in ipairs(legacy_urls) do + local trimmed = trim(url) + if trimmed ~= "" then + local sid_output = luci.sys.exec("uci add shadowsocksr server_subscribe_item") + local sid = sid_output:match("%S+") + if sid and sid ~= "" then + local escaped_sid = luci.util.shellquote(sid) + local alias = string.format("Subscribe %d", index) + local escaped_alias = luci.util.shellquote(alias) + local escaped_url = luci.util.shellquote(trimmed) + luci.sys.call("uci set shadowsocksr." .. escaped_sid .. ".enabled=1") + luci.sys.call("uci set shadowsocksr." .. escaped_sid .. ".alias=" .. escaped_alias) + luci.sys.call("uci set shadowsocksr." .. escaped_sid .. ".url=" .. escaped_url) + end + end + end + + luci.sys.call("uci delete shadowsocksr." .. subscribe_sid .. ".subscribe_url") + luci.sys.call("uci commit shadowsocksr") +end + +local function clash_host_port(clash_url) + if not clash_url or clash_url == "" then + return nil, nil + end + local ok, parsed = pcall(URL.parse, clash_url) + if not ok or not parsed then + return nil, nil + end + local host = parsed.host + local port = parsed.port + if not port or port == "" then + port = (parsed.scheme == "http") and "80" or "443" + end + return host, port +end + +migrate_legacy_subscribe_urls() + uci:foreach("shadowsocksr", "servers", function(s) server_count = server_count + 1 + server_sections[#server_sections + 1] = s[".name"] + server_cache[s[".name"]] = { + type = s.type, + v2ray_protocol = s.v2ray_protocol, + alias = s.alias, + server_port = s.server_port, + server = s.server, + transport = s.transport, + ws_path = s.ws_path, + ws_host = s.ws_host, + tls_host = s.tls_host, + tls = s.tls, + reality = s.reality, + clash_url = s.clash_url + } end) +local function get_server(section) + return server_cache[section] or {} +end + +do + local raw = nixio.fs.readfile("/tmp/ssrplus_server_detect.json") + if raw and raw ~= "" then + local parsed = json.parse(raw) + if type(parsed) == "table" then + detect_cache = parsed + end + end +end + m = Map("shadowsocksr", translate("Servers subscription and manage")) +if upload_errmessage then + m.errmessage = upload_errmessage +elseif upload_message then + m.message = upload_message +end + +local style_section = m:section(SimpleSection) +style_section.template = "shadowsocksr/servers_subscribe_url_style" -- Server Subscribe s = m:section(TypedSection, "server_subscribe") @@ -118,6 +408,13 @@ o = s:option(Flag, "auto_update", translate("Auto Update")) o.rmempty = false o.description = translate("Auto Update Server subscription, GFW list and CHN route") +o = s:option(ListValue, "config_auto_update_mode", translate("Update Mode")) +o:value("0", translate("Appointment Mode")) +o:value("1", translate("Loop Mode")) +o.default = "0" +o.rmempty = true +o:depends("auto_update", "1") + o = s:option(ListValue, "auto_update_week_time", translate("Update cycle (Day/Week)")) o:value('*', translate("Every Day")) o:value("1", translate("Every Monday")) @@ -129,7 +426,7 @@ o:value("6", translate("Every Saturday")) o:value("0", translate("Every Sunday")) o.default = "*" o.rmempty = true -o:depends("auto_update", "1") +o:depends({auto_update = "1", config_auto_update_mode = "0"}) o = s:option(ListValue, "auto_update_day_time", translate("Regular update (Hour)")) for t = 0, 23 do @@ -137,7 +434,7 @@ for t = 0, 23 do end o.default = 2 o.rmempty = true -o:depends("auto_update", "1") +o:depends({auto_update = "1", config_auto_update_mode = "0"}) o = s:option(ListValue, "auto_update_min_time", translate("Regular update (Min)")) for i = 0, 59 do @@ -145,178 +442,83 @@ for i = 0, 59 do end o.default = 30 o.rmempty = true -o:depends("auto_update", "1") +o:depends({auto_update = "1", config_auto_update_mode = "0"}) --- 确保 hy2_type_list 不为空 -if #hy2_type_list > 0 then - local sid = uci:get_first("shadowsocksr", "server_subscribe") - if not sid then - uci:foreach("shadowsocksr", "server_subscribe", function(section) - sid = section[".name"] - return false - end) - end - if sid then - local old_val = uci:get("shadowsocksr", sid, "xray_hy2_type") - if old_val and old_val ~= "" then - if (old_val == "hysteria2" and not has_hysteria2) or - (old_val == "v2ray" and not has_xray) then - -- 核心不可用,设置为空(删除配置) - uci:set("shadowsocksr", sid, "xray_hy2_type", "") - uci:commit("shadowsocksr") - end - end - end - o = s:option(ListValue, "xray_hy2_type", string.format("%s", translatef("%s Node Use Type", "Hysteria2"))) - o.description = translate("The configured type also applies to the core specified when manually importing nodes.") - o:value("", translate("Auto")) - for _, v in ipairs(hy2_type_list) do - o:value(v.id, v.name) -- 存储 "Xray" / "Hysteria2",但 UI 显示完整名称 - end -end - --- 确保 tj_type_list 不为空 -if #tj_type_list > 0 then - local sid = uci:get_first("shadowsocksr", "server_subscribe") - if not sid then - uci:foreach("shadowsocksr", "server_subscribe", function(section) - sid = section[".name"] - return false - end) - end - if sid then - local old_val = uci:get("shadowsocksr", sid, "xray_tj_type") - if old_val and old_val ~= "" then - if (old_val == "trojan" and not has_trojan) or - (old_val == "v2ray" and not has_xray) then - -- 核心不可用,设置为空(删除配置) - uci:set("shadowsocksr", sid, "xray_tj_type", "") - uci:commit("shadowsocksr") - end - end - end - o = s:option(ListValue, "xray_tj_type", string.format("%s", translatef("%s Node Use Type", "Trojan"))) - o.description = translate("The configured type also applies to the core specified when manually importing nodes.") - o:value("", translate("Auto")) - for _, v in ipairs(tj_type_list) do - o:value(v.id, v.name) -- 存储 "Xray" / "Trojan",但 UI 显示完整名称 - end -end - --- 确保 ss_type_list 不为空 -if #ss_type_list > 0 then - local sid = uci:get_first("shadowsocksr", "server_subscribe") - if not sid then - uci:foreach("shadowsocksr", "server_subscribe", function(section) - sid = section[".name"] - return false - end) - end - if sid then - local old_val = uci:get("shadowsocksr", sid, "ss_type") - if old_val and old_val ~= "" then - if (old_val == "ss-rust" and not has_ss_rust) or - (old_val == "ss-libev" and not has_ss_libev) or - (old_val == "v2ray" and not has_xray) then - -- 核心不可用,设置为空(删除配置) - uci:set("shadowsocksr", sid, "ss_type", "") - uci:commit("shadowsocksr") - end - end - end - o = s:option(ListValue, "ss_type", string.format("%s", translatef("%s Node Use Version", "ShadowSocks"))) - o.description = translate("Selection ShadowSocks Node Use Version.") - o:value("", translate("Auto")) - for _, v in ipairs(ss_type_list) do - o:value(v.id, v.name) -- 存储 "ss-libev" / "ss-rust",但 UI 显示完整名称 - end -end - -o = s:option(DynamicList, "subscribe_url", translate("Subscribe URL")) +o = s:option(Value, "config_update_interval", translate("Update Interval(min)")) +o.default = "60" +o.datatype = "uinteger" o.rmempty = true +o:depends({auto_update = "1", config_auto_update_mode = "1"}) -o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve")) -o.description = translate( - "
      " .. - "
    • " .. translate("If the node address is a domain name, this DNS will be used for resolution.") .. "
    • " .. - "
    • " .. string.format('%s', translate("Supports only Xray node types.")) .. "
    • " .. - "
    • " .. string.format('%s', translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead.")) .. "
    • " .. - "
    " -) -o:value("", translate("Auto")) -o:value("tcp", translate("TCP")) -o:value("udp", translate("UDP")) -o:value("https", translate("DoH")) - -o = s:option(Value, "domain_resolver_dns", translate("DNS")) -o.datatype = "or(ipaddr,ipaddrport)" -o:value("114.114.114.114") -o:value("223.5.5.5:53") -o.default = "114.114.114.114" -o:depends("domain_resolver", "tcp") -o:depends("domain_resolver", "udp") - -o = s:option(Value, "domain_resolver_dns_https", translate("DNS")) -o:value("https://120.53.53.53/dns-query", "DNSPod") -o:value("https://223.5.5.5/dns-query", "AliDNS") -o.default = o.keylist[1] -o:depends("domain_resolver", "https") - -o = s:option(ListValue, "domain_strategy", translate("Domain Strategy")) -o.description = translate( - "
      " .. - "
    • " .. translate("If is domain name, The requested domain name will be resolved to IP before connect.") .. "
    • " .. - "
    • " .. string.format('%s', translate("Supports only Xray node types.")) .. "
    • " .. - "
    • " .. string.format('%s', translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead.")) .. "
    • " .. - "
    " -) -o.default = "" -o:value("", translate("Auto")) -o:value("UseIPv4v6", translate("Prefer IPv4")) -o:value("UseIPv6v4", translate("Prefer IPv6")) -o:value("UseIPv4", translate("IPv4 Only")) -o:value("UseIPv6", translate("IPv6 Only")) +o = s:option(Flag, "_subscribe_advanced_toggle", translate("Subscribe Advanced Settings")) +o.rmempty = false +o.default = "0" +o.write = function() end +o.remove = function() end o = s:option(Value, "filter_words", translate("Subscribe Filter Words")) o.rmempty = true o.description = translate("Filter Words splited by /") +o:depends("_subscribe_advanced_toggle", "1") +preserve_when_hidden(o, "_subscribe_advanced_toggle", "1") o = s:option(Value, "save_words", translate("Subscribe Save Words")) o.rmempty = true o.description = translate("Save Words splited by /") - -o = s:option(Button, "update_Sub", translate("Update Subscribe List")) -o.inputstyle = "reload" -o.description = translate("Update subscribe url list first") -o.write = function() - uci:commit("shadowsocksr") - luci.sys.exec("rm -rf /tmp/sub_md5_*") - luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers")) -end +o:depends("_subscribe_advanced_toggle", "1") +preserve_when_hidden(o, "_subscribe_advanced_toggle", "1") o = s:option(Flag, "allow_insecure", translate("Allow subscribe Insecure nodes By default")) o.rmempty = false o.description = translate("Subscribe nodes allows insecure connection as TLS client (insecure)") o.default = "0" +o:depends("_subscribe_advanced_toggle", "1") +preserve_when_hidden(o, "_subscribe_advanced_toggle", "1") o = s:option(Flag, "switch", translate("Subscribe Default Auto-Switch")) o.rmempty = false o.description = translate("Subscribe new add server default Auto-Switch on") o.default = "1" +o:depends("_subscribe_advanced_toggle", "1") +preserve_when_hidden(o, "_subscribe_advanced_toggle", "1") o = s:option(Flag, "proxy", translate("Through proxy update")) o.rmempty = false o.description = translate("Through proxy update list, Not Recommended ") +o.default = "1" +o:depends("_subscribe_advanced_toggle", "1") +preserve_when_hidden(o, "_subscribe_advanced_toggle", "1") + +o = s:option(Button, "_save_subscribe_settings", translate("Save Subscribe Settings")) +o.inputstyle = "save" +o.description = translate("Save current subscribe settings") +o.write = function() end o = s:option(Button, "subscribe", translate("Update All Subscribe Servers")) o.rawhtml = true o.template = "shadowsocksr/subscribe" +o.write = function(self, section) + self.map.ssr_subscribe_requested = true +end o = s:option(Button, "delete", translate("Delete All Subscribe Servers")) o.inputstyle = "reset" o.description = string.format(translate("Server Count") .. ": %d", server_count) o.write = function() - luci.http.redirect(url("delete")) + uci:delete_all("shadowsocksr", "servers", function(s) + if s.hashkey or s.isSubscribe then + return true + else + return false + end + end) + uci:save("shadowsocksr") + uci:commit("shadowsocksr") + for file in nixio.fs.glob("/tmp/sub_md5_*") do + nixio.fs.remove(file) + end + luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "delete")) + return end o = s:option(Value, "url_test_url", translate("URL Test Address")) @@ -335,57 +537,187 @@ o:value("curl", "Curl") o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux") o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows") o:value("v2rayN/9.99", "v2rayN") +o:depends("_subscribe_advanced_toggle", "1") +preserve_when_hidden(o, "_subscribe_advanced_toggle", "1") + +if has_mihomo then + o = s:option(DummyValue, "_upload_clash_yaml", translate("Upload Custom YAML File")) + o.template = "shadowsocksr/clash_yaml_upload" + o.description = translate("Upload a custom Clash/Mihomo YAML file. The file will be preprocessed and saved as a local Clash node.") +end + +s:append(cbi.Template("shadowsocksr/subscribe_schedule_compact")) + +s = m:section(TypedSection, "server_subscribe_item", translate("Subscribe URL")) +s.anonymous = true +s.addremove = true +s.sortable = true +s.template = "shadowsocksr/subscribe_actions_footer" +--s.template = "cbi/tblsection" +--s.template_addremove = "shadowsocksr/subscribe_actions_footer" +s.description = translate("Manage multiple subscribe URLs, including Clash subscriptions. Only enabled entries are included when updating all subscriptions.") + +o = s:option(Flag, "enabled", translate("Enable")) +o.rmempty = false +o.default = "1" +o.width = "1%" +function o.cfgvalue(...) + return Flag.cfgvalue(...) or "1" +end + +o = s:option(Value, "alias", translate("Alias")) +o.rmempty = true +o.width = "7.5rem" +function o.cfgvalue(self, section) + return Value.cfgvalue(self, section) or string.format("Subscribe %s", section:sub(-4)) +end + +o = s:option(Value, "url", translate("Subscribe URL")) +o.rmempty = false -- [[ Servers Manage ]]-- s = m:section(TypedSection, "servers") s.anonymous = true s.addremove = true -s.template = "cbi/tblsection" +s.description = translate("Node order can be dragged with the mouse and takes effect immediately. The automatic switch order of server nodes is consistent with the node order in the table.") +s.template = "shadowsocksr/server_table" set_apply_on_parse(m) +s:append(cbi.Template("shadowsocksr/optimize_cbi_ui")) +s.extedit = luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers", "%s") + +local server_page_size = parse_nonnegative_int(luci.http.formvalue("server_page_size")) +if not server_page_size or not in_list(SERVER_PAGE_SIZE_OPTIONS, server_page_size) then + server_page_size = DEFAULT_SERVER_PAGE_SIZE +end + +local server_page_count = 1 +if server_page_size > 0 and server_count > 0 then + server_page_count = math.ceil(server_count / server_page_size) +end + +local server_page = parse_nonnegative_int(luci.http.formvalue("server_page")) or 1 +if server_page < 1 then + server_page = 1 +end +if server_page > server_page_count then + server_page = server_page_count +end + +local visible_server_sections = server_sections +local server_first_index = server_count > 0 and 1 or 0 +local server_last_index = server_count + +if server_page_size > 0 then + local first = ((server_page - 1) * server_page_size) + 1 + local last = math.min(first + server_page_size - 1, server_count) + visible_server_sections = {} + server_first_index = server_count > 0 and first or 0 + server_last_index = server_count > 0 and last or 0 + + for index = first, last do + visible_server_sections[#visible_server_sections + 1] = server_sections[index] + end +end + s.sortable = true ---[[ -s.extedit = url("servers", "%s") + +function s.cfgsections(self) + return visible_server_sections +end + +s.server_page = server_page +s.server_page_size = server_page_size +s.server_page_count = server_page_count +s.server_page_sizes = SERVER_PAGE_SIZE_OPTIONS +s.server_total = server_count +s.server_first_index = server_first_index +s.server_last_index = server_last_index +s.server_base_url = luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers") + function s.create(self, ...) - local sid = TypedSection.create(self, ...) - if sid then - local newsid = "cfg" .. sid:sub(-6) - -- 删除匿名 + local used_sid = {} + local next_sid = 1 + + self.map.uci:foreach(self.config, self.sectiontype, function(s) + local num = s[".name"]:match("^cfg(%x%x)") + if num then + local n = tonumber(num, 16) + if n then + used_sid[n] = true + end + end + end) + + local function get_next_sid() + while used_sid[next_sid] do + next_sid = next_sid + 1 + end + used_sid[next_sid] = true + return next_sid + end + + local sid = TypedSection.create(self, ...) + if sid then + local suffix = sid:sub(-4) self.map.uci:delete(self.config, sid) - -- 重命名 section - self.map.uci:section(self.config, self.sectiontype, newsid) - luci.http.redirect(self.extedit % newsid) - return - end + local id = get_next_sid() + local newsid = string.format("cfg%02x%s", id, suffix) + local success = self.map.uci:section(self.config, self.sectiontype, newsid) + if success then + --self.map.uci:save(self.config) + luci.http.redirect(self.extedit % newsid) + return + end + end end -]]-- o = s:option(DummyValue, "type", translate("Type")) function o.cfgvalue(self, section) - return m:get(section, "v2ray_protocol") or Value.cfgvalue(self, section) or translate("None") + local cfg = get_server(section) + return cfg.v2ray_protocol or cfg.type or translate("None") end o = s:option(DummyValue, "alias", translate("Alias")) -function o.cfgvalue(...) - return Value.cfgvalue(...) or translate("None") -end - -o = s:option(DummyValue, "server_port", translate("Server Port")) -function o.cfgvalue(...) - return Value.cfgvalue(...) or "N/A" +function o.cfgvalue(self, section) + return get_server(section).alias or translate("None") end o = s:option(DummyValue, "server_port", translate("Socket Connected")) o.template = "shadowsocksr/socket" o.width = "10%" +function o.cfgvalue(self, section) + self.detect_cache = detect_cache[section] + local cfg = get_server(section) + local stype = cfg.type + if stype == "clash" then + return "N/A" + end + return cfg.server_port +end o.render = function(self, section, scope) - local cfg = s:cfgvalue(section) or {} - self.transport = cfg.transport - self.type = cfg.type - self.v2ray_protocol = cfg.v2ray_protocol - if self.transport == 'ws' then - self.ws_path = cfg.ws_path - self.tls = cfg.tls - self.tls_host = cfg.tls_host + local cfg = get_server(section) + local stype = cfg.type + self.type = stype or "" + self.proto = cfg.v2ray_protocol or "" + self.reality = cfg.reality or "" + if stype == "clash" then + self.transport = "" + self.ws_path = "" + self.ws_host = "" + self.tls_host = "" + self.tls = "" + self.reality = "" + else + self.transport = cfg.transport or "" + self.ws_host = cfg.ws_host or "" + self.tls_host = cfg.tls_host or "" + if self.transport == 'ws' then + self.ws_path = cfg.ws_path or "" + self.tls = cfg.tls or "" + else + self.ws_path = "" + self.tls = "" + end end DummyValue.render(self, section, scope) end @@ -393,6 +725,15 @@ end o = s:option(DummyValue, "server", translate("Ping Latency")) o.template = "shadowsocksr/ping" o.width = "10%" +function o.cfgvalue(self, section) + self.detect_cache = detect_cache[section] + local cfg = get_server(section) + self.type = cfg.type or "" + if cfg.type == "clash" then + return "N/A" + end + return cfg.server or "N/A" +end local global_server = uci:get_first('shadowsocksr', 'global', 'global_server') @@ -407,11 +748,10 @@ node.render = function(self, section, scope) Button.render(self, section, scope) end node.write = function(self, section) - uci:set("shadowsocksr", '@global[0]', 'global_server', section) - uci:save("shadowsocksr") - uci:commit("shadowsocksr") - luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &") - luci.http.redirect(url("restart")) + local safe_section = luci.util.shellquote(section) + local cmd = string.format("uci set shadowsocksr.@global[0].global_server=%s && uci commit shadowsocksr", safe_section) + luci.sys.call(cmd) + luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "restart")) end o = s:option(Flag, "switch_enable", translate("Auto Switch")) @@ -422,4 +762,15 @@ end m:append(cbi.Template("shadowsocksr/server_list")) +m.commit_handler = function(self) + if not self.ssr_subscribe_requested then + return + end + + for _, config in ipairs(self.parsechain or {}) do + self.uci:commit(config) + end + self.ssr_subscribe_autostart = true +end + return m diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua index 639486a92..51b563d16 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua @@ -7,15 +7,22 @@ local m, s, o local redir_run = 0 local reudp_run = 0 local sock5_run = 0 +local http_run = 0 local server_run = 0 local kcptun_run = 0 local tunnel_run = 0 local gfw_count = 0 local ad_count = 0 local ip_count = 0 -local nfip_count = 0 -local Process_list = luci.sys.exec("busybox ps -w") +local Process_list = luci.sys.exec("busybox ps -w 2>/dev/null || busybox ps") local uci = require "luci.model.uci".cursor() +local global_server = uci:get_first("shadowsocksr", "global", "global_server", "nil") +local global_type = global_server ~= "nil" and (uci:get("shadowsocksr", global_server, "type") or "") or "" +local global_socks_enabled = uci:get_first("shadowsocksr", "socks5_proxy", "enabled", "0") == "1" +local global_socks_server = uci:get_first("shadowsocksr", "socks5_proxy", "server", "nil") +local global_http_enabled = uci:get_first("shadowsocksr", "http_proxy", "enabled", "0") == "1" +local has_3proxy = nixio.fs.access("/usr/bin/3proxy") or nixio.fs.access("/usr/libexec/3proxy") or nixio.fs.access("/bin/3proxy") +local pdnsd_mode = uci:get_first("shadowsocksr", 'global', 'pdnsd_enable', '0') -- html constants font_blue = [[]] style_blue = [[]] @@ -52,10 +59,6 @@ if nixio.fs.access("/etc/ssrplus/applechina.conf") then apple_count = tonumber(luci.sys.exec("cat /etc/ssrplus/applechina.conf | wc -l")) end -if nixio.fs.access("/etc/ssrplus/netflixip.list") then - nfip_count = tonumber(luci.sys.exec("cat /etc/ssrplus/netflixip.list | wc -l")) -end - if Process_list:find("udp.only.ssr.reudp") then reudp_run = 1 end @@ -74,6 +77,10 @@ if Process_list:find("tcp.udp.ssr.local") then sock5_run = 1 end +if has_3proxy and Process_list:find("3proxy%-ssr%-http%.cfg") then + http_run = 1 +end + if Process_list:find("tcp.udp.ssr.retcp") then redir_run = 1 reudp_run = 1 @@ -103,6 +110,38 @@ if Process_list:find("local.udp.ssr.retcp") then sock5_run = 1 end +if global_type == "socks5" and Process_list:find("ipt2socks") then + if Process_list:find("%-T") or Process_list:find("%-%-tcp%-only") then + redir_run = 1 + end + if Process_list:find("%-U") or Process_list:find("%-%-udp%-only") then + reudp_run = 1 + end + if global_socks_enabled and (global_socks_server == "same" and global_socks_server == global_server) then + sock5_run = 1 + end +end + +if (global_type == "clash" or global_type == "tuic" or global_type == "ss") and Process_list:find("ssr%-retcp") then + redir_run = 1 + reudp_run = 1 + if global_socks_enabled and (global_socks_server == "same" and global_socks_server == global_server) then + sock5_run = 1 + end +end + +if (global_type == "clash" or global_type == "tuic" or global_type == "ss") and Process_list:find("mihomo") and (Process_list:find("/clash%-") or Process_list:find("/tuic%-") or Process_list:find("/ss%-")) then + redir_run = 1 + reudp_run = 1 + if global_socks_enabled and (global_socks_server == "same" and global_socks_server == global_server) then + sock5_run = 1 + end +end + +if has_3proxy and global_http_enabled and http_run == 0 and Process_list:find("3proxy%-ssr%-http%.cfg") then + http_run = 1 +end + if Process_list:find("kcptun.client") then kcptun_run = 1 end @@ -111,11 +150,21 @@ if Process_list:find("ssr.server") then server_run = 1 end +if Process_list:find("mihomo") and Process_list:find("/ss%-server%-") then + server_run = 1 +end + if Process_list:find("ssrplus/bin/dns2tcp") or Process_list:find("ssrplus/bin/mosdns") or - Process_list:find("dnsproxy.*127.0.0.1.*5335") or - Process_list:find("chinadns.*127.0.0.1.*5335") or - (Process_list:find("ssrplus.dns") and Process_list:find("dns2socks.*127.0.0.1.*127.0.0.1.5335")) then + Process_list:find("chinadns.*127.0.0.1.*5335") then + pdnsd_run = 1 +end + +if pdnsd_mode == "7" and (global_type == "clash" or global_type == "tuic" or global_type == "ss") and Process_list:find("ssr%-retcp") then + pdnsd_run = 1 +end + +if pdnsd_mode == "7" and global_type == "v2ray" and Process_list:find("ssr%-retcp%.json") then pdnsd_run = 1 end @@ -157,6 +206,16 @@ else s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off end +if has_3proxy then + s = m:field(DummyValue, "http_run", translate("Global HTTP/HTTPS Proxy Server")) + s.rawhtml = true + if http_run == 1 then + s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off + else + s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off + end +end + s = m:field(DummyValue, "server_run", translate("Local Servers")) s.rawhtml = true if server_run == 1 then @@ -211,13 +270,6 @@ if uci:get_first("shadowsocksr", 'global', 'apple_optimization', '0') ~= '0' the s.value = apple_count .. " " .. translate("Records") end -if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') ~= '0' then - s = m:field(DummyValue, "nfip_data", translate("Netflix IP Data")) - s.rawhtml = true - s.template = "shadowsocksr/refresh" - s.value = nfip_count .. " " .. translate("Records") -end - if uci:get_first("shadowsocksr", 'global', 'adblock', '0') == '1' then s = m:field(DummyValue, "ad_data", translate("Advertising Data")) s.rawhtml = true diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm new file mode 100644 index 000000000..c85245e81 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm @@ -0,0 +1,143 @@ + + + diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm new file mode 100644 index 000000000..724fbe0b1 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm @@ -0,0 +1,1044 @@ +<%+cbi/valueheader%> +<% +local clash_nodes = self.clash_nodes or {} +local current_sid = self.map:get(section, "global_server") or "nil" +-%> +
    + + + + + + + + +
    + +
    +<%+cbi/valuefooter%> diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm new file mode 100644 index 000000000..9d5cb7658 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm @@ -0,0 +1,104 @@ +<%+header%> + +

    <%:Clash Panel%> - <%=alias%>

    + +
    + <%:This panel is available only when the current Clash total node is active.%> +
    + +
    + +
    + +
    +
    + + + +<%+footer%> diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm new file mode 100644 index 000000000..fa9d23eb0 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm @@ -0,0 +1,74 @@ +<%+cbi/valueheader%> +<% +local stype = self.map:get(section, "type") +-%> + +<% if stype == "clash" then %> + + + +<% else %> +- +<% end %> +<%+cbi/valuefooter%> diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_yaml_upload.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_yaml_upload.htm new file mode 100644 index 000000000..830193afb --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_yaml_upload.htm @@ -0,0 +1,4 @@ +<%+cbi/valueheader%> + + +<%+cbi/valuefooter%> diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/client_dns_defaults.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/client_dns_defaults.htm new file mode 100644 index 000000000..9091257af --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/client_dns_defaults.htm @@ -0,0 +1,78 @@ + diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm new file mode 100644 index 000000000..87aee9030 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm @@ -0,0 +1,446 @@ + + + + +
    + <%:Core Components%> +
    +
    +
    +
    +
    +
    +
    + <%:Download Source%> + <%:Address%> +
    + +
    +
    +
    + <%:Component%> + <%:Package Name%> +
    + +
    +
    +
    +
    +
    <%:Collecting data...%>
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + +
    + <%:Geo Database Update%> +
    +
    +
    +
    +
    +
    +
    + <%:Database%> + <%:Geo Resource%> +
    + +
    +
    +
    +
    +
    <%:Collecting data...%>
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/control_layout.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/control_layout.htm new file mode 100644 index 000000000..c73c57c04 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/control_layout.htm @@ -0,0 +1,9 @@ + diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ping.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ping.htm index 5b396f7ce..1db85acbd 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ping.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ping.htm @@ -1,3 +1,17 @@ <%+cbi/valueheader%> --- ms +<% + local hint = self:cfgvalue(section) + local cache = self.detect_cache + local is_clash = self.type == "clash" + local text = is_clash and "N/A" or "--- ms" + local color = is_clash and "#999999" or "#ff0000" + if not is_clash and cache and tonumber(cache.ping or 0) > 0 then + local ping = tonumber(cache.ping) + text = tostring(ping) .. " ms" + if ping < 300 then color = "#ff3300" end + if ping < 200 then color = "#ff7700" end + if ping < 100 then color = "#249400" end + end +%> +<%=text%> <%+cbi/valuefooter%> diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm index 0d0eb3c21..5218cc27f 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm @@ -5,747 +5,414 @@ <% require "luci.sys" function is_js_luci() - return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 + return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 end -%> - - - - - <% if is_js_luci() then -%> <%- else %> <%- end %> - - - - -
    <%:Saving the new order...%>
    -
    - - -
    diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm new file mode 100644 index 000000000..82b9eeaff --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm @@ -0,0 +1,304 @@ +<%- +local rowcnt = 0 + +function rowstyle() + rowcnt = rowcnt + 1 + if rowcnt % 2 == 0 then + return " cbi-rowstyle-1" + else + return " cbi-rowstyle-2" + end +end + +function width(o) + if o.width then + if type(o.width) == "number" then + return ' style="width:%dpx"' % o.width + end + return ' style="width:%s"' % o.width + end + return "" +end + +local has_titles = false +local has_descriptions = false + +local anonclass = (not self.anonymous or self.sectiontitle) and "named" or "anonymous" +local titlename = ifattr(not self.anonymous or self.sectiontitle, "data-title", translate("Name")) + +local i, k +for i, k in pairs(self.children) do + if not k.typename then + k.typename = k.template and k.template:gsub("^.+/", "") or "" + end + + if not has_titles and k.title and #k.title > 0 then + has_titles = true + end + + if not has_descriptions and k.description and #k.description > 0 then + has_descriptions = true + end +end + +local total = tonumber(self.server_total) or 0 +local page = tonumber(self.server_page) or 1 +local page_size = tonumber(self.server_page_size) or 0 +local page_count = tonumber(self.server_page_count) or 1 +local first_index = tonumber(self.server_first_index) or 0 +local last_index = tonumber(self.server_last_index) or 0 +local page_sizes = self.server_page_sizes or {} +local base_url = self.server_base_url or "" + +local function page_url(target_page, target_size) + return string.format( + "%s?server_page=%d&server_page_size=%d", + base_url, + target_page, + target_size + ) +end + +function render_titles() + if not has_titles then + return + end + + %>><% + + local i, k + for i, k in ipairs(self.children) do + if not k.optional then + %>><% + + if k.titleref then + %>" class="cbi-title-ref" href="<%=k.titleref%>"><% + end + + write(k.title) + + if k.titleref then + %><% + end + + %><% + end + end + + if self.sortable or self.extedit or self.addremove then + %><% + end + + %><% + + rowcnt = rowcnt + 1 +end + +function render_descriptions() + if not has_descriptions then + return + end + + %><% + + local i, k + for i, k in ipairs(self.children) do + if not k.optional then + %>><% + + write(k.description) + + %><% + end + end + + if self.sortable or self.extedit or self.addremove then + %><% + end + + %><% + + rowcnt = rowcnt + 1 +end + +function render_pager() + if total == 0 then + return + end + + %> +
    + + <%=string.format(translate("Showing %d-%d of %d nodes"), first_index, last_index, total)%> + + + <% if page_count > 1 then %> + <% if page > 1 then %> + <%:Prev%> + <% end %> + <% for current = 1, page_count do %> + <% if current == page then %> + <%=current%> + <% else %> + <%=current%> + <% end %> + <% end %> + <% if page < page_count then %> + <%:Next%> + <% end %> + <% end %> + + + <%:Per page%>: + <% for _, size in ipairs(page_sizes) do %> + <% + local label = size == 0 and translate("All") or tostring(size) + local selected = size == page_size + %> + <% if selected then %> + <%=label%> + <% else %> + <%=label%> + <% end %> + <% end %> + +
    + + + <% +end +-%> + + + + +
    + <% if self.title and #self.title > 0 then -%> +

    <%=self.title%>

    + <%- end %> + <%- if self.sortable then -%> + + <%- end -%> +
    <%=self.description%>
    + <% render_pager() %> + + <%- + render_titles() + render_descriptions() + + local isempty, section, i, k = true, nil, nil + for i, k in ipairs(self:cfgsections()) do + isempty = false + section = k + + local sectionname = striptags((type(self.sectiontitle) == "function") and self:sectiontitle(section) or k) + local sectiontitle = ifattr(sectionname and (not self.anonymous or self.sectiontitle), "data-title", sectionname, true) + local colorclass = (self.extedit or self.rowcolors) and rowstyle() or "" + local scope = { + valueheader = "cbi/cell_valueheader", + valuefooter = "cbi/cell_valuefooter" + } + -%> + > + <%- + local node + for k, node in ipairs(self.children) do + if not node.optional then + node:render(section, scope or {}) + end + end + -%> + + <%- if self.sortable or self.extedit or self.addremove then -%> + + <%- end -%> + + <%- end -%> + + <%- if isempty then -%> + + + + <%- end -%> +
    +
    + <% if self.extedit then -%> + onclick="location.href='<%=self.extedit:format(section)%>'" + <%- elseif type(self.extedit) == "function" then + %> onclick="location.href='<%=self:extedit(section)%>'" + <%- end + %> alt="<%:Edit%>" title="<%:Edit%>" /> + <% end; if self.addremove then %> + ', { + sid: sid + }, function(x, result) { + result = result || {}; + if (result.ret == 1) { + window.location.reload(); + return; + } + }); + return false; + " + /> + <%- end -%> +
    +
    <%:This section contains no values yet%>
    + <% render_pager() %> + + <% if self.error then %> +
    +
      <% for _, c in pairs(self.error) do for _, e in ipairs(c) do -%> +
    • <%=pcdata(e):gsub("\n", "
      ")%>
    • + <%- end end %>
    +
    + <% end %> + + <%- if self.addremove then -%> + <% if self.template_addremove then include(self.template_addremove) else -%> +
    + <% if self.anonymous then %> + + <% else %> + <% if self.invalid_cts then -%> +
    <%:Invalid%>
    + <%- end %> +
    + +
    + + <% end %> +
    + <%- end %> + <%- end -%> +
    + diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/servers_subscribe_url_style.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/servers_subscribe_url_style.htm new file mode 100644 index 000000000..8451c3d54 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/servers_subscribe_url_style.htm @@ -0,0 +1,166 @@ + diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/socket.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/socket.htm index 74c6b1bf6..e4ce9535c 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/socket.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/socket.htm @@ -1,9 +1,26 @@ <%+cbi/valueheader%> -wait +<% + local hint = self:cfgvalue(section) + local cache = self.detect_cache + local text = "N/A" + local color = "#999999" + if cache then + if cache.socket then + text = "ok" + color = "#249400" + else + text = "fail" + color = "#ff0000" + end + end +%> +<%=text%> + + - - - + + + <%+cbi/valuefooter%> diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm index b5610c2e3..025fd51bf 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm @@ -1,25 +1,15 @@ <%+cbi/valueheader%> <% local map = self.map -local ss_type = map:get("@server_subscribe[0]", "ss_type") -local xray_hy2_type = map:get("@server_subscribe[0]", "xray_hy2_type") -local xray_tj_type = map:get("@server_subscribe[0]", "xray_tj_type") local has_ss_rust = luci.sys.exec('type -t -p sslocal 2>/dev/null || type -t -p ssserver 2>/dev/null') ~= "" -local has_ss_libev = luci.sys.exec('type -t -p ss-redir 2>/dev/null || type -t -p ss-local 2>/dev/null') ~= "" -local has_hysteria = luci.sys.exec('type -t -p hysteria 2>/dev/null') ~= "" -local has_trojan = luci.sys.exec('type -t -p trojan 2>/dev/null') ~= "" +local has_mihomo = luci.sys.exec('type -t -p mihomo -p /usr/libexec/mihomo 2>/dev/null') ~= "" local has_xray = luci.sys.exec('type -t -p xray 2>/dev/null') ~= "" -%> - - +<% +local dsp = require "luci.dispatcher" +local subscribe_url = dsp.build_url("admin", "services", "shadowsocksr", "subscribe") +local get_log_url = dsp.build_url("admin", "services", "shadowsocksr", "get_log") +local servers_url = dsp.build_url("admin", "services", "shadowsocksr", "servers") +local autostart = self.map and self.map.ssr_subscribe_autostart +-%> +
    + + + + + /> + +
    + +
    <%+cbi/valuefooter%> diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm new file mode 100644 index 000000000..e26d03989 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm @@ -0,0 +1,490 @@ +<%- +local rowcnt = 0 + +local function rowstyle() + rowcnt = rowcnt + 1 + if rowcnt % 2 == 0 then + return " cbi-rowstyle-1" + end + return " cbi-rowstyle-2" +end + +local function width(o) + if o.width then + if type(o.width) == "number" then + return ' style="width:%dpx"' % o.width + end + return ' style="width:%s"' % o.width + end + return "" +end +-%> + +
    + <% if self.title and #self.title > 0 then -%> +

    <%=self.title%>

    + <%- end %> + <%- if self.sortable then -%> + + <%- end -%> +
    <%=self.description%>
    + + + <% for _, node in ipairs(self.children) do if not node.optional and node.option ~= "_update" then -%> + + <% end end -%> + + + + <% + local isempty = true + for i, section in ipairs(self:cfgsections()) do + isempty = false + local colorclass = rowstyle() + local scope = { + valueheader = "cbi/cell_valueheader", + valuefooter = "cbi/cell_valuefooter" + } + -%> + + <% + for _, node in ipairs(self.children) do + if not node.optional and node.option ~= "_update" then + node:render(section, scope) + end + end + local alias = self.map:get(section, "alias") or string.format("Subscribe %s", section:sub(-4)) + local update_title = "Subscribe: " .. tostring(alias) + -%> + + + <%- end -%> + + <%- if isempty then -%> + + + + <%- end -%> +
    ><%=node.title%><%:Actions%>
    +
    + + + + onclick="return window.ssrUpdateSubscribeItem(this)" + /> +
    +
    <%:This section contains no values yet%>
    +
    + +
    + + + +
    +
    +
    + + + + diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_enabled_autosave.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_enabled_autosave.htm new file mode 100644 index 000000000..905727f74 --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_enabled_autosave.htm @@ -0,0 +1,47 @@ + diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm new file mode 100644 index 000000000..1290eb2fc --- /dev/null +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm @@ -0,0 +1,182 @@ + + + diff --git a/luci-app-ssr-plus/po/templates/ssr-plus.pot b/luci-app-ssr-plus/po/templates/ssr-plus.pot index 266a80823..9b28a7554 100644 --- a/luci-app-ssr-plus/po/templates/ssr-plus.pot +++ b/luci-app-ssr-plus/po/templates/ssr-plus.pot @@ -1,254 +1,267 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:361 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:231 msgid "" "\"1-3\" is for segmentation at TCP layer, applying to the beginning 1 to 3 " "data writes by the client. \"tlshello\" is for TLS client hello packet " "fragmentation." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:279 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:310 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:170 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:198 -msgid "%s Node Use Type" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:409 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:227 -msgid "%s Node Use Version" -msgstr "" - #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:103 msgid "0" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:100 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:94 msgid "1 Thread" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:107 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:101 msgid "128 Threads" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1523 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1570 msgid "16" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:104 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:98 msgid "16 Threads" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:101 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:95 msgid "2 Threads" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:105 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:99 msgid "32 Threads" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1354 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1390 msgid "360" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:270 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:207 msgid "360 Security DNS (China Telecom) (101.226.4.6)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:271 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:208 msgid "360 Security DNS (China Unicom) (123.125.81.6)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:102 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:96 msgid "4 Threads" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:106 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:100 msgid "64 Threads" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1510 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1557 msgid "8" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:103 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:97 msgid "8 Threads" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:390 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:260 msgid "" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1031 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1369 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1396 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1061 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1416 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1443 msgid "" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:38 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:51 msgid "" -"

    Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN " +"

    Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/CLASH " "etc.

    " msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:160 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:186 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:220 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1203 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1503 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1516 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1529 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1655 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1682 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:188 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:214 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:249 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:240 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:267 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1233 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1550 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1563 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1576 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:186 msgid "
    • " msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:58 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:354 +msgid "A legal file path. This file must not exist before running." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:387 +msgid "Accept LAN Access" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:627 msgid "Access Control" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:178 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:206 -msgid "AdGuard DNSCrypt SDNS" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1035 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:235 +msgid "Actions" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:749 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1015 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:290 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:298 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:189 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:284 msgid "Add" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:59 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:215 +msgid "Add failed:" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:182 +msgid "Adding..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:221 +msgid "Additional Version" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:378 +msgid "Address" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:628 msgid "Advanced Settings" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:222 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:274 msgid "Advertising Data" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1676 -msgid "AliDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:268 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:205 msgid "AliYun Public DNS (223.5.5.5)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:367 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:568 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:680 msgid "Alias" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:397 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:373 msgid "Alias(optional)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:119 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:157 +msgid "All" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:112 msgid "All Ports" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:39 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:965 +msgid "All client proxy rules cleared and applied." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:965 +msgid "All client proxy rules cleared." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:101 +msgid "All settings saved successfully." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:35 msgid "Allow all except listed" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:38 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:34 msgid "Allow listed only" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:297 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:471 msgid "Allow subscribe Insecure nodes By default" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:877 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:34 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:120 +msgid "Already up to date" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:186 +msgid "Alter ID" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:907 msgid "AlterId" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1271 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1307 msgid "An FinalMaskObject in JSON format, used for sharing." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:142 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:173 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:149 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:170 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:201 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:235 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:134 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:152 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:171 msgid "Anti-pollution DNS Server" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:125 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:206 -msgid "Anti-pollution DNS Server For Shunt Mode" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:243 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:128 msgid "Apple Domains DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:208 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:267 msgid "Apple Domains Data" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:238 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:123 msgid "Apple Domains Update url" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:234 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:119 msgid "Apple domains optimization" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:399 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:405 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:740 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:746 msgid "Apply" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:391 -msgid "Are you sure to delete this node?" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:412 +msgid "Appointment Mode" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:112 +msgid "Are you sure you want to delete subscribe link: %s ?" msgstr "" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:133 msgid "Are you sure you want to restore the client to default settings?" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:282 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:313 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1660 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1688 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:172 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:200 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:229 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:246 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:274 -msgid "Auto" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:417 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:757 msgid "Auto Switch" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:99 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:93 msgid "Auto Threads" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:117 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:407 msgid "Auto Update" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:119 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:409 msgid "Auto Update Server subscription, GFW list and CHN route" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:811 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1569 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1585 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:841 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1616 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1632 msgid "BBR" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1570 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1586 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1617 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1633 msgid "BRUTAL" msgstr "" @@ -260,59 +273,72 @@ msgstr "" msgid "Backup or Restore Client and Server Configurations." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:193 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:252 msgid "Baidu Connectivity" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:269 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:206 msgid "Baidu Public DNS (180.76.76.76)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:320 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:297 msgid "Base64 sstr failed." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1154 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1164 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:378 +msgid "Bind Local Only" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1184 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1194 msgid "BitTorrent (uTP)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:100 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:96 msgid "Black Domain List" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:536 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:530 msgid "Bloom Filter" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:84 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:80 msgid "Bypass Domain List" msgstr "" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:261 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:439 +msgid "CHECK AND UPDATE" +msgstr "" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:31 msgid "CLOSE WIN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:272 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:209 msgid "CNNIC SDNS (1.2.4.8)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:812 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1571 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1589 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:842 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1618 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1636 msgid "CUBIC" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1171 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1201 msgid "Camouflage Domain" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:939 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1160 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:969 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1190 msgid "Camouflage Type" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1479 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:227 +msgid "Certificate File Path" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1526 msgid "Certificate fingerprint" msgstr "" @@ -326,15 +352,26 @@ msgstr "" msgid "Check Server" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:228 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:280 msgid "Check Server Port" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:77 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:94 msgid "Check Try Count" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:72 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:157 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:405 +msgid "Check Update" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua:6 +msgid "" +"Check installed component versions and upgrade them online from the upstream " +"release page." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:89 msgid "Check timout(second)" msgstr "" @@ -343,72 +380,127 @@ msgstr "" msgid "Check..." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:202 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:146 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:250 +msgid "Checking..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:261 msgid "China IP Data" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:255 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:192 msgid "ChinaDNS-NG query protocol" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:226 -msgid "ChinaDNS-NG shunt query protocol" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:89 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:113 msgid "Chnroute Update url" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:90 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:114 msgid "Clang.CN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:91 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:115 msgid "Clang.CN.CIDR" msgstr "" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:376 +msgid "Clash" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:88 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:817 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1013 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:3 +msgid "Clash Panel" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:392 +msgid "Clash Subscription URL" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:404 +msgid "Clash User-Agent" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:398 +msgid "Clash YAML Path" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:352 +msgid "Clash/Mihomo" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1018 +msgid "Clear All Rules" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:954 +msgid "Clear all custom client proxy rules and restore empty state?" +msgstr "" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/log.htm:35 msgid "Clear logs" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:164 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:192 -msgid "Click here to view or manage the DNS list file" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:394 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1033 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1371 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1398 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:264 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1063 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1418 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1445 msgid "Click to the page" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:148 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:176 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1032 +msgid "Client" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:920 +msgid "Client rules exported to CSV file." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:944 +msgid "Client rules imported from CSV file. Click Save to apply." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1027 +msgid "Client supports manual IP or IP/CIDR input." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1024 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:251 +msgid "Close" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:158 msgid "Cloudflare DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:136 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:217 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:160 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:246 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:145 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:182 msgid "Cloudflare DNS (1.1.1.1)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:179 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:207 -msgid "Cloudflare DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:20 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:402 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:436 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:21 msgid "Collecting data..." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1031 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:390 +msgid "Component" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:630 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua:5 +msgid "Component Update" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1061 msgid "Configure XHTTP Extra Settings (JSON format), see:" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:809 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:839 msgid "Congestion control algorithm" msgstr "" @@ -420,211 +512,227 @@ msgstr "" msgid "Connect OK" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:103 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:82 msgid "Connection Timeout" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1382 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1429 msgid "" "Controls the policy used when performing DNS queries for ECH configuration." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:93 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:90 msgid "Copy SSR to clipboard successfully." msgstr "" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:369 +msgid "Core Components" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:20 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:428 +msgid "Country MMDB" +msgstr "" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:2 msgid "Create Backup File" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1617 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1664 msgid "Create upload file error." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1637 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:110 +msgid "Current ARCH" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1684 msgid "Current Certificate Path" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:562 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:219 +msgid "Current Version" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:564 msgid "Custom" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:182 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:210 -msgid "" -"Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-query " -"and other format)." +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:991 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:992 +msgid "Custom Client Proxies Rules" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:139 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:221 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:166 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:250 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:148 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:187 msgid "Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:278 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:212 msgid "Custom DNS Server format as IP:PORT (default: disabled)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:150 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:178 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:160 msgid "" "Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT " "(tcp://8.8.8.8 or tls://dns.google:853)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:566 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:568 msgid "Custom Plugin Path" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:121 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:114 msgid "Custom Ports" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1272 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:269 +msgid "Custom YAML imported successfully: %s" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:708 +msgid "Custom client proxy rules saved and applied." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:708 +msgid "Custom client proxy rules saved." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1308 msgid "" "Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related " "settings." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:103 -msgid "Customize Netflix IP Url" -msgstr "" - #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:4 msgid "DL Backup" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1168 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1666 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1674 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:251 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:259 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1198 msgid "DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:143 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:192 msgid "DNS Anti-pollution" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:106 -msgid "DNS Query Mode For Shunt Mode" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1675 -msgid "DNSPod" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:267 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:204 msgid "DNSPod Public DNS (119.29.29.29)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1156 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1166 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1186 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1196 msgid "DTLS 1.2" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1218 -msgid "Decimal numbers separated by \",\" or Base64-encoded strings." +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:424 +msgid "Database" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1246 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1419 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1431 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1445 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm:87 +msgid "Day/Week" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:193 +msgid "Decryption" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1276 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1466 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1478 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1492 msgid "Default" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1530 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:101 +msgid "Default Node Local Port" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1577 msgid "Default reject rejects traffic." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:707 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:737 msgid "Default value 0 indicatesno heartbeat." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1504 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1551 msgid "" "Default: disable. When entering a negative number, such as -1, The Mux " "module will not be used to carry TCP traffic." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1517 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1564 msgid "" "Default:16. When entering a negative number, such as -1, The Mux module will " "not be used to carry UDP traffic, Use original UDP transmission method of " "proxy protocol." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:184 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:212 -msgid "Defines the upstreams logic mode" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:297 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:41 +msgid "Defaults Cleared!" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:187 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:215 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:297 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:41 +msgid "Defaults Restored!" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1027 msgid "" -"Defines the upstreams logic mode, possible values: load_balance, parallel, " -"fastest_addr (default: load_balance)." +"Define per-client Mihomo proxy targets. Rules are injected as SRC-IP-CIDR " +"entries before the original Clash rules." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:432 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:302 msgid "Delay (ms)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:738 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:589 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:248 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:261 msgid "Delete" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:315 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:504 msgid "Delete All Subscribe Servers" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:116 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:130 +msgid "Delete failed:" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:112 msgid "Deny Domain List" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:62 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:70 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:78 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:37 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:663 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:81 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:33 msgid "Disable" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:263 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:200 msgid "Disable ChinaDNS-NG" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:152 -msgid "Disable IPv6 In MosDNS Query Mode (Shunt Mode)" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:163 +msgid "Disable IPv6 for Overseas FQDN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:180 -msgid "Disable IPv6 in MOSDNS query mode" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:197 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:226 -msgid "Disable IPv6 query mode" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:664 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:694 msgid "Disable QUIC path MTU discovery" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:853 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:883 msgid "Disable SNI" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:734 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:764 msgid "Disable TCP No_delay" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:149 -msgid "Dnsproxy Parse List" -msgstr "" - #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:18 msgid "Do Reset" msgstr "" @@ -633,119 +741,114 @@ msgstr "" msgid "Do you want to restore the client to default settings?" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1663 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:249 -msgid "DoH" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:230 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:259 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:196 msgid "DoT upstream (Need use wolfssl version)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1653 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:238 -msgid "Domain DNS Resolve" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:419 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1680 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:265 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:289 msgid "Domain Strategy" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:262 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:353 +msgid "DomainSocket Path" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:199 msgid "Domestic DNS Server" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1189 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1219 msgid "Downlink Capacity(Default:Mbps)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:739 -msgid "Drag to reorder" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:377 +msgid "Download Source" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:864 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:43 +msgid "Download failed" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:894 msgid "Dual-stack Listening Socket" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1367 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1414 msgid "ECH Config" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1381 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1428 msgid "ECH Query Policy" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:981 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1011 msgid "Early Data Header Name" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:737 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:240 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:246 msgid "Edit" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:259 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:69 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:307 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:40 msgid "Edit ShadowSocksR Server" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:271 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:408 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:82 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:157 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:198 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:278 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:54 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:101 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:560 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1031 msgid "Enable" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:858 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:888 msgid "Enable 0-RTT QUIC handshake" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:482 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:474 msgid "Enable Authentication" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:63 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1712 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:80 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1701 msgid "Enable Auto Switch" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1362 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1398 msgid "Enable ECH(optional)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:640 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:657 msgid "Enable Lazy Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1389 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1436 msgid "Enable ML-DSA-65(optional)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1548 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1595 msgid "" "Enable Multipath TCP, need to be enabled in both server and client " "configuration." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1484 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1531 msgid "Enable Mux.Cool" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:95 -msgid "Enable Netflix Mode" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:634 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:651 msgid "Enable Obfuscation" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:542 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:536 msgid "Enable Plugin" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:602 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:619 msgid "Enable Port Hopping" msgstr "" @@ -753,200 +856,258 @@ msgstr "" msgid "Enable Server" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:615 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:632 msgid "Enable Transport Protocol Settings" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:720 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:750 msgid "Enable V2 protocol." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:719 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:749 msgid "Enable V3 protocol." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:249 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:134 msgid "Enable adblock" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:531 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:525 msgid "Enable the SUoT protocol, requires server support." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1024 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1054 msgid "Enable this option to configure XHTTP Extra (JSON format)." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1201 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1231 msgid "Enabled Kernel virtual NIC TUN(optional)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:335 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:181 msgid "Enabled Mixed" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:729 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1643 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:759 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1690 msgid "Enabling TCP Fast Open Requires Server Support." msgstr "" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:503 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:510 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:517 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:761 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:772 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:903 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:118 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:125 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:122 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:791 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:802 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:933 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:103 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:108 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:171 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:127 msgid "Encrypt Method" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:122 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:115 msgid "Enter Custom Ports" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:122 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:419 msgid "Every Day" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:127 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:424 msgid "Every Friday" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:123 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:420 msgid "Every Monday" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:128 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:425 msgid "Every Saturday" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:129 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:426 msgid "Every Sunday" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:126 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:423 msgid "Every Thursday" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:124 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:421 msgid "Every Tuesday" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:125 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:422 msgid "Every Wednesday" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:289 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:606 +msgid "Example:" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:223 msgid "Expecting: %s" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:86 -msgid "External Proxy Mode" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1019 +msgid "Export Client Rules" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1587 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:44 +msgid "Extract failed" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1634 msgid "FORCE BRUTAL" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:282 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:967 +msgid "Failed to clear all client proxy rules." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:42 +msgid "Failed to create temp directory" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:230 +msgid "Failed to create temporary YAML upload file." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:37 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:77 +msgid "Failed to fetch release metadata" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:99 +msgid "Failed to query component information." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:210 +msgid "Failed to query geo database information." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:710 +msgid "Failed to save custom client proxy rules." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:219 +msgid "File Not Exist" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:461 msgid "Filter Words splited by /" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1260 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1290 msgid "FinalMask" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1346 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1382 msgid "Finger Print" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1333 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1369 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:212 msgid "Flow" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:234 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:119 msgid "" "For Apple domains equipped with Chinese mainland CDN, always responsive to " "Chinese CDN IP addresses" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:392 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:262 msgid "For specific usage, see:" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:609 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:626 msgid "" "Format as 10000:20000 or 10000-20000 Multiple groups are separated by commas " "(,)." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:88 -msgid "Forward Netflix Proxy through Main Proxy" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:358 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:228 msgid "Fragment" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:375 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:245 msgid "Fragment Delay" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:370 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:240 msgid "Fragment Length" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:361 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:231 msgid "Fragment Packets" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:375 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:245 msgid "Fragmentation interval (ms)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:370 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:240 msgid "Fragmented packet length (byte)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:197 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:256 msgid "GFW List Data" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:112 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:106 msgid "GFW List Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:68 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:64 msgid "Game Mode Host List" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:134 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:183 msgid "Game Mode UDP Relay" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:69 -msgid "Game Mode UDP Server" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:829 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:859 msgid "Garbage collection interval(second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:835 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:865 msgid "Garbage collection lifetime(second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:126 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:416 +msgid "Geo Database Update" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:425 +msgid "Geo Resource" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:24 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:429 +msgid "GeoSite Database" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua:14 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:381 +msgid "GitHub Direct" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:175 msgid "Global Client" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:114 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:194 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:210 +msgid "Global HTTP/HTTPS Proxy Server" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:108 msgid "Global Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:267 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:152 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:152 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:201 msgid "Global SOCKS5 Proxy Server" msgstr "" @@ -954,31 +1115,25 @@ msgstr "" msgid "Global Setting" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:189 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:76 +msgid "Go to relevant configuration page" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:248 msgid "Google Connectivity" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:174 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:202 -msgid "Google DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:143 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:171 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:153 msgid "Google Public DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:126 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:207 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:150 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:236 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:135 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:172 msgid "Google Public DNS (8.8.4.4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:127 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:208 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:151 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:237 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:136 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:173 msgid "Google Public DNS (8.8.8.8)" msgstr "" @@ -986,481 +1141,582 @@ msgstr "" msgid "Grant UCI access for luci-app-ssr-plus" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1095 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1125 msgid "Gun" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1114 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1144 msgid "H2 Read Idle Timeout" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1109 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1139 msgid "H2/gRPC Health Check" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:453 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:942 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:421 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:972 msgid "HTTP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:946 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:202 +msgid "HTTP Auth Mode" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:976 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:291 msgid "HTTP Host" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:951 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:212 +msgid "HTTP Password" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:981 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:295 msgid "HTTP Path" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1077 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:208 +msgid "HTTP User" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:202 +msgid "HTTP proxy auth method, default:none." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1107 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:268 msgid "HTTP/2 Host" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1082 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1112 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:274 msgid "HTTP/2 Path" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1149 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1179 msgid "Header" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1126 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1156 msgid "Health Check Timeout" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:817 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:847 msgid "Heartbeat interval(second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:968 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:998 msgid "HeartbeatPeriod(second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:990 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm:88 +msgid "Hour" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1020 msgid "Httpupgrade Host" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:995 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1025 msgid "Httpupgrade Path" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:284 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:357 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:450 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:84 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:407 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:418 msgid "Hysteria2" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:658 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:598 +msgid "Hysteria2 Realms" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:688 msgid "Hysterir QUIC parameters" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:113 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:107 msgid "IP Route Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1443 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1490 msgid "IP Stack Preference" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1691 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:277 -msgid "IPv4 Only" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1692 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:278 -msgid "IPv6 Only" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:243 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:128 msgid "If empty, Not change Apple domains parsing DNS (Default is empty)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1683 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:268 -msgid "" -"If is domain name, The requested domain name will be resolved to IP before " -"connect." -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1369 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1416 msgid "" "If it is not empty, it indicates that the Client has enabled Encrypted " "Client, see:" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1656 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:241 -msgid "" -"If the node address is a domain name, this DNS will be used for resolution." -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:865 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:895 msgid "If this option is not set, the socket behavior is platform dependent." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1456 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1503 msgid "" "If true, allowss insecure connection at TLS client, e.g., TLS server uses " "unverifiable certificates." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1601 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1648 msgid "If you have a self-signed certificate,please check the box" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1193 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1088 msgid "Import" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:249 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:425 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:597 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:672 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:706 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:813 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:952 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1104 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1184 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1020 +msgid "Import Client Rules" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:195 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:376 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:520 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:590 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:624 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:731 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:847 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:999 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1079 msgid "Import configuration information successfully." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1102 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1132 msgid "Initial Windows Size" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:17 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:48 +msgid "Install failed" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:108 +msgid "Installed Version" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:49 +msgid "Installed binary failed to run" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:13 msgid "Interface" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:16 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:12 msgid "Interface control" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1187 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:293 +msgid "Invalid" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1082 msgid "Invalid format." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:172 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:330 +msgid "KCP Congestion Control" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:324 +msgid "KCP Downlink Capacity" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:306 +msgid "KCP MTU" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:335 +msgid "KCP Read Buffer Size" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:347 +msgid "KCP Seed" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:312 +msgid "KCP TTI" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:318 +msgid "KCP Uplink Capacity" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:341 +msgid "KCP Write Buffer Size" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:231 msgid "KcpTun" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1722 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1706 msgid "KcpTun Enable" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1739 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1726 msgid "KcpTun Param" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1734 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1720 msgid "KcpTun Password" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1728 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1713 msgid "KcpTun Port" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:169 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:228 msgid "KcpTun Version" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:36 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:32 msgid "LAN Access Control" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:52 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:48 msgid "LAN Bypassed Host List" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:60 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:56 msgid "LAN Force Proxy Host List" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:42 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:38 msgid "LAN Host List" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:34 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:30 msgid "LAN IP AC" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:130 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:211 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:154 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:240 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:109 +msgid "Latest Version" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:139 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:176 msgid "Level 3 Public DNS (209.244.0.3)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:131 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:212 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:155 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:241 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:140 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:177 msgid "Level 3 Public DNS (209.244.0.4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:132 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:213 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:156 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:242 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:141 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:178 msgid "Level 3 Public DNS (4.2.2.1)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:133 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:214 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:157 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:243 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:142 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:179 msgid "Level 3 Public DNS (4.2.2.2)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:134 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:215 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:158 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:244 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:143 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:180 msgid "Level 3 Public DNS (4.2.2.3)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:135 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:216 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:159 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:245 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:144 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:181 msgid "Level 3 Public DNS (4.2.2.4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:145 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:173 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:155 msgid "Level 3 Public DNS-1 (209.244.0.3-4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:146 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:174 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:156 msgid "Level 3 Public DNS-2 (4.2.2.1-2)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:147 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:175 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:157 msgid "Level 3 Public DNS-3 (4.2.2.3-4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:380 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:250 msgid "Limit the maximum number of splits." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1204 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1234 msgid "" "Linux kernel TUN virtual NIC requires system support and root privileges." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:22 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:18 msgid "Listen only on the given interface or, if unspecified, on all" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:348 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1716 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:718 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:771 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:84 +msgid "Loading..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:187 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:217 msgid "Local Port" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:160 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:219 msgid "Local Servers" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1212 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1242 msgid "Local addresses" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:66 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:643 msgid "Log" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:85 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:413 +msgid "Loop Mode" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:109 msgid "Loukky/gfwlist-by-loukky" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:84 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:108 msgid "Loyalsoldier/v2ray-rules-dat" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1394 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1441 msgid "ML-DSA-65 Public key" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1548 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1595 msgid "MPTCP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1175 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1205 msgid "MTU" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:61 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:80 msgid "Main Server" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:974 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:558 +msgid "" +"Manage multiple subscribe URLs, including Clash subscriptions. Only enabled " +"entries are included when updating all subscriptions." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:38 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:78 +msgid "Matching release asset not found" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1004 msgid "Max Early Data" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:380 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:250 msgid "Max Split" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:870 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:900 msgid "Maximum packet size the socks5 server can receive from external" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1518 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:372 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:375 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:378 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:9 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:395 +msgid "Mihomo" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1005 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1006 +msgid "Mihomo Pannel" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm:89 +msgid "Min" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1565 msgid "" "Min value is 1, Max value is 1024. When omitted or set to 0, Will same path " "as TCP traffic." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1505 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1552 msgid "" "Min value is 1, Max value is 128. When omitted or set to 0, it equals 8." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:335 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua:13 +msgid "Mirror URL" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:40 +msgid "Missing gzip support" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:39 +msgid "Missing unzip support" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:41 +msgid "Missing xz support" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:181 msgid "Mixed as an alias of socks, default:Enabled." msgstr "" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/optimize_cbi_ui.htm:10 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:735 msgid "Move down" msgstr "" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/optimize_cbi_ui.htm:7 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:734 msgid "Move up" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:222 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:251 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:188 msgid "Muitiple DNS server can saperate with ','" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1096 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1126 msgid "Multi" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:98 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:92 msgid "Multi Threads Option" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1066 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1290 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1096 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1326 msgid "Must be JSON text!" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1484 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1531 msgid "Mux" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:253 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:138 msgid "NEO DEV HOST" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:10 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:11 msgid "NOT RUNNING" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:354 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:401 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:349 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:13 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:396 msgid "NaiveProxy" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:266 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:27 +msgid "Name" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:203 msgid "Nanjing Xinfeng 114DNS (114.114.114.114)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:132 -msgid "Netflix Domain List" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:215 -msgid "Netflix IP Data" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:100 -msgid "Netflix IP Only" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:77 -msgid "Netflix Node" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:101 -msgid "Netflix and AWS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:369 -msgid "Network Tunnel" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:399 -msgid "Network interface to use" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:813 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:843 msgid "New Reno" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:190 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:194 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:230 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:149 +msgid "Next" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:249 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:253 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:282 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:116 msgid "No Check" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:209 -msgid "No available core (Hysteria2 or Xray) to import this node." +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:156 +msgid "No available Xray core to import this Hysteria2 node." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:632 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:546 msgid "No available core (Shadowsocks or Xray) to import this node." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:842 -msgid "No available core (Trojan or Xray) to import this node." +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:738 +msgid "No available core (Xray) to import this node." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:249 +msgid "No custom YAML file was selected." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:474 +msgid "No custom client proxy rules yet." msgstr "" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm:21 msgid "No new data!" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1633 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1680 msgid "No specify upload file." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:385 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:94 +msgid "No subscription items found" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:615 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:32 +msgid "No switchable proxy groups found." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:582 +msgid "" +"Node order can be dragged with the mouse and takes effect immediately. The " +"automatic switch order of server nodes is consistent with the node order in " +"the table." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:255 msgid "Noise" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:549 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:941 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1140 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1152 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1162 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:364 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:369 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:544 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:971 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1170 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1182 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1192 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:677 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:682 msgid "None" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:131 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:139 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:148 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:157 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:165 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:177 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:180 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:188 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:197 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:206 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:215 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:224 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:236 msgid "Not Running" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:28 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:35 msgid "Not exist" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1657 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1684 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:243 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:270 -msgid "Note: For node-specific DNS only. Keep Auto to avoid extra overhead." +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:108 +msgid "Not installed" msgstr "" #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/log.lua:27 @@ -1469,116 +1725,120 @@ msgid "" "compatibility issues." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1559 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1606 msgid "Number of early established connections to reduce latency." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:548 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:584 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:139 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:133 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:543 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:586 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:118 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:138 msgid "Obfs" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:591 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:146 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:593 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:123 msgid "Obfs param (optional)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1196 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1226 msgid "Obfuscate password (optional)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:651 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:670 msgid "Obfuscation Password" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:645 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:662 msgid "Obfuscation Type" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1468 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1515 msgid "Once set, connects only when the server’s chain fingerprint matches." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:120 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:113 msgid "Only Common Ports" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:324 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:208 +msgid "Only when HTTP Auth Mode is password valid, Mandatory." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:212 +msgid "Only when HTTP Auth Mode is password valid, Not mandatory." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:170 msgid "Only when Socks5 Auth Mode is password valid, Mandatory." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:329 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:175 msgid "Only when Socks5 Auth Mode is password valid, Not mandatory." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:144 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:172 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:154 msgid "OpenDNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:129 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:210 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:153 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:239 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:138 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:175 msgid "OpenDNS (208.67.220.220)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:128 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:209 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:152 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:238 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:137 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:174 msgid "OpenDNS (208.67.222.222)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:115 -msgid "Oversea Mode" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:391 +msgid "Package Name" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:161 -msgid "Oversea Mode DNS-1 (114.114.114.114)" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:162 -msgid "Oversea Mode DNS-2 (114.114.115.115)" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:391 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:261 msgid "Packet or Rand length as a string, e.g., 10-20." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:428 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:298 msgid "Packet | Rand Length" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:496 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:114 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:68 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:69 +msgid "Panel" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:488 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:93 msgid "Password" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:105 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:102 msgid "Paste sharing link here" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1227 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1257 msgid "Peer public key" msgstr "" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:154 +msgid "Per page" +msgstr "" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:14 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:23 msgid "Perform reset" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1132 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1162 msgid "Permit Without Stream" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:393 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:725 msgid "Ping Latency" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1640 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1687 msgid "Please confirm the current certificate path" msgstr "" @@ -1586,96 +1846,108 @@ msgstr "" msgid "Please fill in reset" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:570 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:387 +msgid "Please specify either a Clash subscription URL or a local YAML path." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:572 msgid "Plugin Opts" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:626 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1033 +msgid "Policy" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:643 msgid "Port Hopping Interval(Unit:Second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:608 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:625 msgid "Port hopping range" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1559 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1606 msgid "Pre-connections" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1231 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1261 msgid "Pre-shared key" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1689 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:275 -msgid "Prefer IPv4" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:130 +msgid "Prefer module built-in DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1690 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:276 -msgid "Prefer IPv6" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:139 +msgid "Prev" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:93 -msgid "Prefer firewall tools" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:232 +msgid "Private Key File Path" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1222 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1252 msgid "Private key" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:574 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:132 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:128 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:150 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:254 +msgid "Processing..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:576 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:113 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:133 msgid "Protocol" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:581 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:583 msgid "Protocol param (optional)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:118 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:111 msgid "Proxy Ports" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1320 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1356 msgid "Public key" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1145 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:371 +msgid "QUIC Camouflage Type" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1175 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:367 msgid "QUIC Key" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1138 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1168 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:359 msgid "QUIC Security" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:685 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:715 msgid "QUIC initConnReceiveWindow" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:671 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:701 msgid "QUIC initStreamReceiveWindow" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:692 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:722 msgid "QUIC maxConnReceiveWindow" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:699 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:729 msgid "QUIC maxIdleTimeout(Unit:second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:678 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:708 msgid "QUIC maxStreamReceiveWindow" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:177 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:205 -msgid "Quad9 DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1315 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1351 msgid "REALITY" msgstr "" @@ -1683,33 +1955,59 @@ msgstr "" msgid "RST Backup" msgstr "" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:429 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:7 msgid "RUNNING" msgstr "" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:422 +msgid "RUNNING in %s (%s) Mode" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:426 +msgid "RUNNING in %s Mode" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:604 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:752 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:802 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:93 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:228 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:253 +msgid "Ready." +msgstr "" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:5 msgid "Really reset all changes?" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:403 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:609 +msgid "Realm STUN" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:606 +msgid "Realm URL" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:744 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:272 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:17 msgid "Reapply" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:200 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:205 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:211 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:218 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:225 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:259 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:264 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:270 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:277 msgid "Records" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:406 -msgid "Redirect traffic to this network interface" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:10 +msgid "Refresh" msgstr "" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm:29 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm:35 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:11 msgid "Refresh Data" msgstr "" @@ -1722,45 +2020,90 @@ msgid "Refresh OK!" msgstr "" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm:6 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:5 msgid "Refresh..." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:134 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:431 msgid "Regular update (Hour)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:142 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:439 msgid "Regular update (Min)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1572 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1588 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:111 +msgid "Release Asset" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:56 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:57 +msgid "Reload YAML" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:265 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:10 +msgid "Reloading YAML..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:133 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:134 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1034 +msgid "Remarks" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1619 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1635 msgid "Reno" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1217 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:154 +msgid "Required for VMess/VLESS. Generate with: uuidgen" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1247 msgid "Reserved bytes(optional)" msgstr "" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:998 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:999 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:62 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:63 +msgid "Reset Default Proxies Rules" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:300 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:44 +msgid "Reset Defaults Failed!" +msgstr "" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:17 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:18 msgid "Reset complete" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:259 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:286 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:30 +msgid "Reset saved Mihomo proxy-group selections and restore YAML defaults?" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:144 msgid "Reset to defaults" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:127 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:290 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:34 +msgid "Resetting Defaults..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:120 msgid "Resolve Dns Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:182 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:241 msgid "Restart Service" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:181 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:240 msgid "Restart ShadowSocksR Plus+" msgstr "" @@ -1773,91 +2116,92 @@ msgstr "" msgid "Restore to default configuration" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:129 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:137 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:146 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:155 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:163 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:175 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:178 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:186 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:195 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:204 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:213 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:222 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:234 msgid "Running" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:111 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:105 msgid "Running Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:342 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:403 +msgid "SOCKS5" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:301 msgid "SS URL base64 sstr format not recognized." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:56 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:625 msgid "SSR Client" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:60 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:629 msgid "SSR Server" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:277 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:71 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:79 -msgid "Same as Global Server" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1023 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:260 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:285 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:286 +msgid "Save" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:750 -msgid "Save Order" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:492 +msgid "Save Subscribe Settings" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:286 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:467 msgid "Save Words splited by /" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:484 -msgid "Save failed!" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:494 +msgid "Save current subscribe settings" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:482 -msgid "Saved current page order successfully." +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:82 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:105 +msgid "Save failed:" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:747 -msgid "Saving the new order..." +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:687 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:51 +msgid "Saving..." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:158 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:186 -msgid "Select DNS parse Mode" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm:77 +msgid "Second" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:410 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:228 -msgid "Selection ShadowSocks Node Use Version." -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1593 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1640 msgid "Self-signed Certificate" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:276 -msgid "Server" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:456 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:424 msgid "Server Address" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:317 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:506 msgid "Server Count" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:340 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:223 +msgid "Server Name (SNI)" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:335 msgid "Server Node Type" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:469 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:96 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:112 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:372 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:449 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:75 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:117 msgid "Server Port" msgstr "" @@ -1865,306 +2209,358 @@ msgstr "" msgid "Server Setting" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:86 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:58 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:107 msgid "Server Type" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:56 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:73 msgid "Server failsafe auto swith and custom update settings" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:57 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:626 msgid "Servers Nodes" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:111 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:393 msgid "Servers subscription and manage" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1309 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1345 msgid "Session Ticket" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:167 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:195 -msgid "Set Single DNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:363 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:358 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:558 msgid "Shadow-TLS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:746 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:776 msgid "Shadow-TLS ChainPoxy type" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:348 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:445 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:89 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:343 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:346 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:413 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:61 msgid "ShadowSocks" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:415 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:101 -msgid "ShadowSocks-libev Version" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:412 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:749 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:98 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:779 msgid "ShadowSocks-rust Version" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:53 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:622 msgid "ShadowSocksR Plus+" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:38 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:51 msgid "ShadowSocksR Plus+ Settings" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:757 -msgid "Shadowsocks password" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:405 +msgid "ShadowTLS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:345 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:92 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:373 +msgid "Shadowsocks" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:787 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:166 +msgid "Shadowsocks Password" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:383 +msgid "Shadowsocks-rust" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:381 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:340 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:64 msgid "ShadowsocksR" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1324 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1360 msgid "Short ID" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:377 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:134 +msgid "Showing %d-%d of %d nodes" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:685 msgid "Socket Connected" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:452 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:420 msgid "Socks" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:911 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:941 msgid "Socks Version" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:310 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:163 msgid "Socks protocol auth methods, default:noauth." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:366 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:87 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:361 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:59 msgid "Socks5" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:310 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:163 msgid "Socks5 Auth Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:329 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:175 msgid "Socks5 Password" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:324 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:170 msgid "Socks5 User" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:151 -msgid "Specifically for edit dnsproxy DNS parse files." +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:73 +msgid "Start Detection" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:61 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:205 +msgid "Starting subscription update..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:631 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:114 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:116 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:118 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:120 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:224 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:226 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:228 msgid "Status" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:302 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:74 +msgid "Stop Detection" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:453 +msgid "Subscribe Advanced Settings" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:478 msgid "Subscribe Default Auto-Switch" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:280 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:459 msgid "Subscribe Filter Words" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:284 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:250 +msgid "Subscribe Log" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:465 msgid "Subscribe Save Words" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:235 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:551 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:575 msgid "Subscribe URL" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:304 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:194 +msgid "Subscribe failed, refreshing page..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:480 msgid "Subscribe new add server default Auto-Switch on" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:299 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:473 msgid "Subscribe nodes allows insecure connection as TLS client (insecure)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:257 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:142 msgid "Support AdGuardHome and DNSMASQ format list" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:627 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:644 msgid "Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:242 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:269 -msgid "Supports only Xray node types." +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1248 +msgid "" +"Supports decimal numbers separated by \",\" or Base64-encoded strings, with " +"a maximum length of 3 bytes." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:67 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:84 msgid "Switch check cycly(second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1661 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:247 -msgid "TCP" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:654 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:71 +msgid "Switch failed." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:729 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1643 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:149 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:651 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:68 +msgid "Switched successfully." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:644 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:61 +msgid "Switching..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:282 +msgid "TCP Camouflage Type" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:759 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1690 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:126 msgid "TCP Fast Open" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:358 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:228 msgid "" "TCP fragments, which can deceive the censorship system in some cases, such " "as bypassing SNI blacklists." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:228 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:257 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:194 msgid "TCP upstream" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1295 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1331 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:199 msgid "TLS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:724 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:754 msgid "TLS 1.3 Strict mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1417 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1464 msgid "TLS ALPN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1473 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1520 msgid "TLS Certificate Name (CertName)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1468 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1515 msgid "TLS Chain Fingerprint (SHA256)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1409 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1456 msgid "TLS Host" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:539 -msgid "TLS handshake test, latency for reference only" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1473 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1520 msgid "TLS is used to verify the leaf certificate name." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:360 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:379 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:355 msgid "TUIC" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1429 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1476 msgid "TUIC ALPN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:789 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:819 msgid "TUIC Server IP Address" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:796 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:826 msgid "TUIC User Password" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:782 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:812 msgid "TUIC User UUID" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:847 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:877 msgid "TUIC receive window" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:841 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:871 msgid "TUIC send window" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:175 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:203 -msgid "TWNIC-101 DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:539 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:604 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:628 -msgid "Test" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:581 -msgid "Testing..." -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1396 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1443 msgid "" "The client has not configured mldsa65Verify, but it will not perform the " "\"additional verification\" step and can still connect normally, see:" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:280 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:311 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:171 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:199 -msgid "" -"The configured type also applies to the core specified when manually " -"importing nodes." -msgstr "" - #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:10 msgid "The content entered is incorrect!" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:706 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:736 msgid "The keep-alive period.(Unit:second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:307 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:6 +msgid "" +"The online upgrade downloads the matching Xray-core linux archive for the " +"current ARCH from the official GitHub release page." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:10 +msgid "" +"The online upgrade only tracks the latest stable Mihomo release and " +"downloads the matching linux archive for the current ARCH from the official " +"GitHub release page." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:14 +msgid "" +"The online upgrade tracks the latest NaiveProxy release and prefers OpenWrt " +"static archives matching the current ARCH before falling back to other " +"release assets." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:740 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:795 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:89 +msgid "This Clash total node is not active. Apply this node first." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:6 +msgid "" +"This panel is available only when the current Clash total node is active." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:272 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:126 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:277 +msgid "This section contains no values yet" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:485 msgid "Through proxy update" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:309 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:487 msgid "Through proxy update list, Not Recommended" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:823 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm:78 +msgid "Timeout" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:853 msgid "Timeout for establishing a connection to server(second)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:162 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:190 -msgid "Tips: Dnsproxy DNS Parse List Path:" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:736 -msgid "To Bottom" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:733 -msgid "To Top" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:390 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:260 msgid "To send noise packets, select \"Noise\" in Xray Settings." msgstr "" @@ -2172,53 +2568,57 @@ msgstr "" msgid "Total Records:" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:920 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:950 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:177 msgid "Transport" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:620 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:637 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:238 msgid "Transport Protocol" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:315 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:351 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:444 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:91 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:399 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:412 msgid "Trojan" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:412 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:362 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:160 +msgid "Trojan Password" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm:79 +msgid "Try Count" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:282 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:674 msgid "Type" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:622 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1662 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:248 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:639 msgid "UDP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:385 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:255 msgid "" "UDP noise, Under some circumstances it can bypass some UDP based protocol " "restrictions." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:530 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:524 msgid "UDP over TCP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:802 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:832 msgid "UDP relay mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:229 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:258 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:195 msgid "UDP upstream" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:227 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:256 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:193 msgid "UDP/TCP upstream" msgstr "" @@ -2226,494 +2626,558 @@ msgstr "" msgid "UL Restore" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:322 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:524 msgid "URL Test Address" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:95 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:153 +msgid "UUID" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:92 msgid "Unable to copy SSR to clipboard." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:25 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:35 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:32 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:42 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:109 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:110 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:111 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:226 msgid "Unknown" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:311 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:16 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:80 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:82 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:130 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:215 +msgid "Unknown error" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:36 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:76 +msgid "Unsupported ARCH" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:79 +msgid "Unsupported component" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:265 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:266 +msgid "Update" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:497 msgid "Update All Subscribe Servers" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:288 -msgid "Update Subscribe List" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:447 +msgid "Update Interval(min)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:121 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:411 +msgid "Update Mode" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:418 msgid "Update cycle (Day/Week)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:290 -msgid "Update subscribe url list first" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:29 +msgid "" +"Update geoip.dat and geosite.dat used by Xray/V2Ray runtime when available." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1182 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:21 +msgid "" +"Update the Country.mmdb database used by Mihomo/Clash runtime when available." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:25 +msgid "" +"Update the GeoSite.dat database used by Mihomo/Clash runtime when available." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:31 +msgid "Updating..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:157 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:406 +msgid "Upgrade" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:118 +msgid "Upgrade available" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:35 +msgid "Upgrade completed" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:146 +msgid "Upgrading..." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1212 msgid "Uplink Capacity(Default:Mbps)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1603 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1650 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/certupload.htm:3 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_yaml_upload.htm:3 msgid "Upload" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:120 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:144 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:544 +msgid "Upload Custom YAML File" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:546 +msgid "" +"Upload a custom Clash/Mihomo YAML file. The file will be preprocessed and " +"saved as a local Clash node." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:263 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:271 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:276 +msgid "Uploaded YAML validation or preprocessing failed." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:128 msgid "Use ChinaDNS-NG query and cache" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:168 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:196 -msgid "Use DNS List File" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:264 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:201 msgid "Use DNS from WAN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:265 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:202 msgid "Use DNS from WAN and 114DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:108 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:132 -msgid "Use DNS2SOCKS query and cache" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:111 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:135 -msgid "Use DNS2SOCKS-RUST query and cache" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:129 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:122 msgid "Use DNS2TCP query" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:117 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:141 -msgid "Use DNSPROXY query and cache" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:146 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:131 msgid "Use Local DNS Service listen port 5335" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:138 -msgid "Use MOSDNS query (Not Support Oversea Mode)" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:114 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:125 msgid "Use MosDNS query" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1172 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1202 msgid "" "Use it together with the DNS disguised type. You can fill in any domain." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:107 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:142 +msgid "User Level" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:104 msgid "User cancelled." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1244 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:332 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1274 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:534 msgid "User-Agent" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:295 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1133 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:313 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1028 msgid "Userinfo format error." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:489 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:110 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:117 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:481 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:89 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:122 msgid "Username" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:596 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:613 msgid "Users Authentication" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:395 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:371 msgid "Using incorrect encryption mothod may causes service fail to start" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:342 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:28 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:430 +msgid "V2Ray GEO Databases" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:337 msgid "V2Ray/XRay" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:441 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:409 msgid "V2Ray/XRay protocol" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:442 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:410 msgid "VLESS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:892 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:922 msgid "VLESS Encryption" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:443 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:411 msgid "VMess" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1153 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1163 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1183 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1193 msgid "VideoCall (SRTP)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:752 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:782 msgid "Vmess Protocol" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:767 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:797 msgid "Vmess UUID" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:884 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:914 msgid "Vmess/VLESS ID (UUID)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:30 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:26 msgid "WAN Force Proxy IP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:25 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:21 msgid "WAN IP AC" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:27 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:23 msgid "WAN White List IP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:957 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:987 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:252 msgid "WebSocket Host" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:963 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:993 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:259 msgid "WebSocket Path" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1155 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1165 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1185 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1195 msgid "WechatVideo" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:96 -msgid "When disabled shunt mode, will same time stopped shunt service." -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:198 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:227 -msgid "When disabled, all AAAA requests are not resolved." -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1205 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1235 msgid "When enabled, it occupies IPv6 routing table 1023." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:187 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:216 -msgid "When two or more DNS servers are deployed, enable this function." +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:388 +msgid "When selected, it can be accessed from LAN. This may not be safe!" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:161 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:189 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:379 msgid "" -"When use DNS list file, please ensure list file exists and is formatted " -"correctly." +"When selected, it can only be accessed locally. Recommended when using " +"reverse proxies." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:447 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1157 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1167 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:415 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1187 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1197 msgid "WireGuard" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1237 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1267 msgid "Wireguard allows only traffic from specific source IP." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1023 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1053 msgid "XHTTP Extra" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1011 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1041 msgid "XHTTP Host" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1002 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1032 msgid "XHTTP Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1017 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1047 msgid "XHTTP Path" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:287 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:87 -msgid "Xray (Hysteria2)" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:207 +msgid "XTLS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:104 -msgid "Xray (ShadowSocks)" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:394 +msgid "Xray" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:318 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:94 -msgid "Xray (Trojan)" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:355 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:225 msgid "Xray Fragment Settings" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:388 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:258 msgid "Xray Noise Packets" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:252 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:5 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:394 +msgid "Xray-core" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:275 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:20 +msgid "YAML Reload Failed!" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:272 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:17 +msgid "YAML Reloaded!" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:137 msgid "adblock_url" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1141 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1171 msgid "aes-128-gcm" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1537 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1584 msgid "allow" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1531 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1578 msgid "allow: Allows use Mux connection." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1451 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1498 msgid "allowInsecure" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1236 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1266 msgid "allowedIPs(optional)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1352 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1388 msgid "android" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:254 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:139 msgid "anti-AD" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1142 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1172 msgid "chacha20-poly1305" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:92 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:116 msgid "china-operator-ip" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1247 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1348 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1277 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1384 msgid "chrome" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:180 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:208 -msgid "cloudflare-dns.com DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1568 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1584 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1615 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1631 msgid "comment_tcpcongestion_disable" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1501 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1548 msgid "concurrency" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:678 -msgid "connect" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1252 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1282 msgid "curl" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1565 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1581 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1612 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1628 msgid "custom_tcpcongestion" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1358 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1509 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1522 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1394 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1556 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1569 msgid "disable" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:176 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:204 -msgid "dns.sb DNSCrypt SDNS" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:228 +msgid "e.g.: /etc/ssl/fullchain.pem" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1250 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1353 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:233 +msgid "e.g.: /etc/ssl/private.key" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1280 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1389 msgid "edge" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:192 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:221 -msgid "fastest_addr" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:239 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:124 msgid "felixonmars/dnsmasq-china-list" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1248 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1349 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1278 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1385 msgid "firefox" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1120 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1150 msgid "gRPC Idle Timeout" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1093 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1123 msgid "gRPC Mode" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1087 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1117 msgid "gRPC Service Name" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:82 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:106 msgid "gfwlist Update url" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:86 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:110 msgid "gfwlist/gfwlist" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1251 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1281 msgid "golang" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1351 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:552 +msgid "gost-plugin" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1387 msgid "ios" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:190 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:219 -msgid "load_balance" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:562 +msgid "kcptun" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:805 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:835 msgid "lossless UDP relay using QUIC streams" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:804 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:300 +msgid "mKCP Camouflage Type" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:46 +msgid "mihomo binary not found in archive" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:47 +msgid "naive binary not found in archive" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:834 msgid "native UDP characteristics" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:99 -msgid "nfip_url" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:521 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1337 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:514 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1373 msgid "none" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:551 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:546 msgid "obfs-local" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:191 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:220 -msgid "parallel" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1355 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1391 msgid "qq" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1356 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1392 msgid "random" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1357 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1393 msgid "randomized" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1536 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1583 msgid "reject" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1249 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1350 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:561 +msgid "restls" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1279 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1386 msgid "safari" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:739 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:769 msgid "shadow-TLS SNI" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:560 -msgid "shadow-tls" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:717 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:747 msgid "shadowTLS protocol Version" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1538 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1585 msgid "skip" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1532 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1579 msgid "" "skip: Not use Mux module to carry UDP 443 traffic, Use original UDP " "transmission method of proxy protocol." msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1328 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1364 msgid "spiderX" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:83 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:107 msgid "v2fly/domain-list-community" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:554 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:549 msgid "v2ray-plugin" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:289 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:223 msgid "valid address:port" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:101 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:80 msgid "warning! Please do not reuse the port!" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:557 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:45 +msgid "xray binary not found in archive" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:555 msgid "xray-plugin" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1514 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1561 msgid "xudpConcurrency" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1527 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1574 msgid "xudpProxyUDP443" msgstr "" diff --git a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po index d5e4cc392..250a18ce3 100644 --- a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po +++ b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po @@ -1,7 +1,7 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8\n" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:361 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:231 msgid "" "\"1-3\" is for segmentation at TCP layer, applying to the beginning 1 to 3 " "data writes by the client. \"tlshello\" is for TLS client hello packet " @@ -10,249 +10,262 @@ msgstr "" "\"1-3\" 是 TCP 的流切片,应用于客户端第 1 至第 3 次写数据。\"tlshello\" 是 " "TLS 握手包切片。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:279 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:310 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:170 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:198 -msgid "%s Node Use Type" -msgstr "%s 节点使用类型" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:409 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:227 -msgid "%s Node Use Version" -msgstr "%s 节点使用版本" - #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:103 msgid "0" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:100 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:94 msgid "1 Thread" msgstr "单线程" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:107 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:101 msgid "128 Threads" msgstr "128 线程" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1523 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1570 msgid "16" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:104 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:98 msgid "16 Threads" msgstr "16 线程" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:101 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:95 msgid "2 Threads" msgstr "2 线程" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:105 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:99 msgid "32 Threads" msgstr "32 线程" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1354 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1390 msgid "360" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:270 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:207 msgid "360 Security DNS (China Telecom) (101.226.4.6)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:271 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:208 msgid "360 Security DNS (China Unicom) (123.125.81.6)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:102 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:96 msgid "4 Threads" msgstr "4 线程" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:106 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:100 msgid "64 Threads" msgstr "64 线程" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1510 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1557 msgid "8" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:103 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:97 msgid "8 Threads" msgstr "8 线程" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:390 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:260 msgid "" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1031 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1369 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1396 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1061 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1416 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1443 msgid "" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:38 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:51 msgid "" -"

      Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN " +"

      Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/CLASH " "etc.

      " msgstr "" -"

      支持 SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN 等协" +"

      支持 SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/CLASH 等协" "议。

      " -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:160 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:186 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:220 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1203 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1503 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1516 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1529 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1655 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1682 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:188 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:214 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:249 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:240 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:267 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1233 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1550 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1563 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1576 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:186 msgid "
      • " msgstr "" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:58 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:354 +msgid "A legal file path. This file must not exist before running." +msgstr "一个合法的文件路径。在运行之前,此文件必须不存在。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:387 +msgid "Accept LAN Access" +msgstr "接受局域网访问" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:627 msgid "Access Control" msgstr "访问控制" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:178 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:206 -msgid "AdGuard DNSCrypt SDNS" -msgstr "" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1035 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:235 +msgid "Actions" +msgstr "操作" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:749 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1015 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:290 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:298 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:189 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:284 msgid "Add" msgstr "添加" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:59 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:215 +msgid "Add failed:" +msgstr "添加失败:" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:182 +msgid "Adding..." +msgstr "添加中..." + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:221 +msgid "Additional Version" +msgstr "附加版本" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:378 +msgid "Address" +msgstr "地址" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:628 msgid "Advanced Settings" msgstr "高级设置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:222 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:274 msgid "Advertising Data" msgstr "【广告屏蔽】数据库" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1676 -msgid "AliDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:268 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:205 msgid "AliYun Public DNS (223.5.5.5)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:367 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:568 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:680 msgid "Alias" msgstr "别名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:397 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:373 msgid "Alias(optional)" msgstr "别名(可选)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:119 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:157 +msgid "All" +msgstr "全部" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:112 msgid "All Ports" msgstr "所有端口(默认)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:39 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:965 +msgid "All client proxy rules cleared and applied." +msgstr "所有客户端代理规则已清空并生效。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:965 +msgid "All client proxy rules cleared." +msgstr "所有客户端代理规则已清空。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:101 +msgid "All settings saved successfully." +msgstr "所有设置保存成功。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:35 msgid "Allow all except listed" msgstr "除列表外主机皆允许" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:38 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:34 msgid "Allow listed only" msgstr "仅允许列表内主机" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:297 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:471 msgid "Allow subscribe Insecure nodes By default" msgstr "订阅节点允许不验证 TLS 证书" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:877 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:34 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:120 +msgid "Already up to date" +msgstr "已是最新版本" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:186 +msgid "Alter ID" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:907 msgid "AlterId" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1271 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1307 msgid "An FinalMaskObject in JSON format, used for sharing." msgstr "JSON 格式的 FinalMaskObject,用来实现分享。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:142 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:173 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:149 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:170 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:201 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:235 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:134 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:152 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:171 msgid "Anti-pollution DNS Server" msgstr "访问国外域名 DNS 服务器" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:125 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:206 -msgid "Anti-pollution DNS Server For Shunt Mode" -msgstr "分流模式下的访问国外域名 DNS 服务器" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:243 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:128 msgid "Apple Domains DNS" msgstr "Apple 域名 DNS" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:208 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:267 msgid "Apple Domains Data" msgstr "【Apple 域名】数据库" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:238 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:123 msgid "Apple Domains Update url" msgstr "Apple 域名更新 URL" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:234 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:119 msgid "Apple domains optimization" msgstr "Apple 域名解析优化" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:399 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:405 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:740 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:746 msgid "Apply" msgstr "应用" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:391 -msgid "Are you sure to delete this node?" -msgstr "是否真的要删除该节点?" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:412 +msgid "Appointment Mode" +msgstr "定时模式" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:112 +msgid "Are you sure you want to delete subscribe link: %s ?" +msgstr "确定要删除此订阅链接:%s 吗?" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:133 msgid "Are you sure you want to restore the client to default settings?" msgstr "是否真的要恢复客户端默认配置?" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:282 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:313 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1660 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1688 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:172 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:200 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:229 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:246 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:274 -msgid "Auto" -msgstr "自动" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:417 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:757 msgid "Auto Switch" msgstr "自动切换" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:99 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:93 msgid "Auto Threads" msgstr "自动(CPU 线程数)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:117 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:407 msgid "Auto Update" msgstr "自动更新" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:119 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:409 msgid "Auto Update Server subscription, GFW list and CHN route" msgstr "自动更新服务器订阅、GFW 列表和中国大陆 IP 段" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:811 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1569 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1585 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:841 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1616 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1632 msgid "BBR" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1570 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1586 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1617 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1633 msgid "BRUTAL" msgstr "" @@ -264,59 +277,72 @@ msgstr "备份还原" msgid "Backup or Restore Client and Server Configurations." msgstr "备份或还原客户端及服务端配置。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:193 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:252 msgid "Baidu Connectivity" msgstr "【百度】连通性检查" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:269 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:206 msgid "Baidu Public DNS (180.76.76.76)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:320 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:297 msgid "Base64 sstr failed." msgstr "Base64 解码失败。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1154 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1164 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:378 +msgid "Bind Local Only" +msgstr "仅绑定本地端口" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1184 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1194 msgid "BitTorrent (uTP)" msgstr "BT 下载(uTP)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:100 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:96 msgid "Black Domain List" msgstr "强制走代理的域名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:536 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:530 msgid "Bloom Filter" msgstr "布隆过滤器" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:84 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:80 msgid "Bypass Domain List" msgstr "不走代理的域名" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:261 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:439 +msgid "CHECK AND UPDATE" +msgstr "检查并更新" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:31 msgid "CLOSE WIN" msgstr "关闭窗口" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:272 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:209 msgid "CNNIC SDNS (1.2.4.8)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:812 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1571 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1589 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:842 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1618 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1636 msgid "CUBIC" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1171 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1201 msgid "Camouflage Domain" msgstr "伪装域名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:939 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1160 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:969 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1190 msgid "Camouflage Type" msgstr "伪装类型" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1479 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:227 +msgid "Certificate File Path" +msgstr "证书文件路径" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1526 msgid "Certificate fingerprint" msgstr "证书指纹" @@ -330,15 +356,26 @@ msgstr "检查连通性" msgid "Check Server" msgstr "检查服务器" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:228 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:280 msgid "Check Server Port" msgstr "【服务器端口】检查" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:77 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:94 msgid "Check Try Count" msgstr "切换检查重试次数" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:72 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:157 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:405 +msgid "Check Update" +msgstr "检查更新" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua:6 +msgid "" +"Check installed component versions and upgrade them online from the upstream " +"release page." +msgstr "检测已安装组件版本,并从上游发布页在线升级。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:89 msgid "Check timout(second)" msgstr "切换检查超时时间(秒)" @@ -347,72 +384,127 @@ msgstr "切换检查超时时间(秒)" msgid "Check..." msgstr "正在检查..." -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:202 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:146 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:250 +msgid "Checking..." +msgstr "检查中..." + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:261 msgid "China IP Data" msgstr "【中国大陆 IP 段】数据库" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:255 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:192 msgid "ChinaDNS-NG query protocol" msgstr "ChinaDNS-NG 查询协议" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:226 -msgid "ChinaDNS-NG shunt query protocol" -msgstr "ChinaDNS-NG 分流查询协议" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:89 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:113 msgid "Chnroute Update url" msgstr "中国大陆 IP 段更新 URL" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:90 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:114 msgid "Clang.CN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:91 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:115 msgid "Clang.CN.CIDR" msgstr "" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:376 +msgid "Clash" +msgstr "Clash" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:88 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:817 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1013 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:3 +msgid "Clash Panel" +msgstr "Clash 面板" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:392 +msgid "Clash Subscription URL" +msgstr "Clash 订阅链接" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:404 +msgid "Clash User-Agent" +msgstr "Clash User-Agent" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:398 +msgid "Clash YAML Path" +msgstr "Clash YAML 路径" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:352 +msgid "Clash/Mihomo" +msgstr "Clash/Mihomo" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1018 +msgid "Clear All Rules" +msgstr "清空所有规则" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:954 +msgid "Clear all custom client proxy rules and restore empty state?" +msgstr "清空所有自定义客户端代理规则,并恢复为空白状态吗?" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/log.htm:35 msgid "Clear logs" msgstr "清空日志" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:164 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:192 -msgid "Click here to view or manage the DNS list file" -msgstr "点击此处查看或管理 DNS 列表文件" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:394 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1033 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1371 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1398 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:264 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1063 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1418 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1445 msgid "Click to the page" msgstr "点击前往" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:148 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:176 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1032 +msgid "Client" +msgstr "客户端" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:920 +msgid "Client rules exported to CSV file." +msgstr "客户端规则已导出为 CSV 文件。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:944 +msgid "Client rules imported from CSV file. Click Save to apply." +msgstr "客户端规则已从 CSV 文件导入,请点击保存生效。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1027 +msgid "Client supports manual IP or IP/CIDR input." +msgstr "客户端支持手动输入 IP 或 IP/CIDR。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1024 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:251 +msgid "Close" +msgstr "关闭" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:158 msgid "Cloudflare DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:136 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:217 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:160 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:246 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:145 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:182 msgid "Cloudflare DNS (1.1.1.1)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:179 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:207 -msgid "Cloudflare DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:20 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:402 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:436 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:21 msgid "Collecting data..." msgstr "正在收集数据中..." -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1031 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:390 +msgid "Component" +msgstr "组件" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:630 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua:5 +msgid "Component Update" +msgstr "组件升级" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1061 msgid "Configure XHTTP Extra Settings (JSON format), see:" msgstr "配置 XHTTP 额外设置(JSON 格式),具体请参见:" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:809 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:839 msgid "Congestion control algorithm" msgstr "拥塞控制算法" @@ -424,142 +516,158 @@ msgstr "连接错误" msgid "Connect OK" msgstr "连接正常" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:103 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:82 msgid "Connection Timeout" msgstr "连接超时" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1382 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1429 msgid "" "Controls the policy used when performing DNS queries for ECH configuration." msgstr "控制使用 DNS 查询 ECH 配置时的策略。" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:93 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:90 msgid "Copy SSR to clipboard successfully." msgstr "成功复制 SSR 网址到剪贴板。" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:369 +msgid "Core Components" +msgstr "核心组件" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:20 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:428 +msgid "Country MMDB" +msgstr "Country MMDB" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:2 msgid "Create Backup File" msgstr "创建备份文件" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1617 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1664 msgid "Create upload file error." msgstr "创建上传文件错误。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1637 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:110 +msgid "Current ARCH" +msgstr "当前架构" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1684 msgid "Current Certificate Path" msgstr "当前证书路径" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:562 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:219 +msgid "Current Version" +msgstr "当前版本" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:564 msgid "Custom" msgstr "自定义" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:182 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:210 -msgid "" -"Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-query " -"and other format)." -msgstr "" -"自定义 DNS 服务器(支持格式:IP:端口、tls://IP:端口、https://IP/dns-query 及" -"其他格式)。" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:991 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:992 +msgid "Custom Client Proxies Rules" +msgstr "自定义客户端代理规则" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:139 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:221 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:166 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:250 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:148 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:187 msgid "Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)" msgstr "格式为 IP:Port(默认:8.8.4.4:53)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:278 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:212 msgid "Custom DNS Server format as IP:PORT (default: disabled)" msgstr "格式为 IP:PORT(默认:禁用)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:150 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:178 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:160 msgid "" "Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT " "(tcp://8.8.8.8 or tls://dns.google:853)" msgstr "" "格式为tcp://IP:Port或tls://域名:Port (tcp://8.8.8.8或tls://dns.google:853)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:566 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:568 msgid "Custom Plugin Path" msgstr "自定义插件路径" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:121 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:114 msgid "Custom Ports" msgstr "自定义端口" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1272 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:269 +msgid "Custom YAML imported successfully: %s" +msgstr "自定义 YAML 导入成功:%s" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:708 +msgid "Custom client proxy rules saved and applied." +msgstr "自定义客户端代理规则已保存并生效。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:708 +msgid "Custom client proxy rules saved." +msgstr "自定义客户端代理规则已保存。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1308 msgid "" "Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related " "settings." msgstr "自定义 finalmask 将覆盖 mkcp、hysteria2、fragment、noise 等相关配置。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:103 -msgid "Customize Netflix IP Url" -msgstr "" -"自定义 Netflix IP 段更新 URL(默认项目地址:https://github.com/QiuSimons/" -"Netflix_IP)" - #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:4 msgid "DL Backup" msgstr "下载备份" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1168 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1666 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1674 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:251 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:259 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1198 msgid "DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:143 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:192 msgid "DNS Anti-pollution" msgstr "DNS 防污染服务" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:106 -msgid "DNS Query Mode For Shunt Mode" -msgstr "分流模式下的 DNS 查询模式" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1675 -msgid "DNSPod" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:267 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:204 msgid "DNSPod Public DNS (119.29.29.29)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1156 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1166 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1186 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1196 msgid "DTLS 1.2" msgstr "DTLS 1.2 数据包" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1218 -msgid "Decimal numbers separated by \",\" or Base64-encoded strings." -msgstr "用“,”隔开的十进制数字或 Base64 编码字符串。" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:424 +msgid "Database" +msgstr "数据库" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1246 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1419 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1431 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1445 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm:87 +msgid "Day/Week" +msgstr "日/周" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:193 +msgid "Decryption" +msgstr "解密" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1276 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1466 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1478 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1492 msgid "Default" msgstr "默认" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1530 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:101 +msgid "Default Node Local Port" +msgstr "节点默认本地端口" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1577 msgid "Default reject rejects traffic." msgstr "默认 reject 拒绝流量。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:707 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:737 msgid "Default value 0 indicatesno heartbeat." msgstr "默认为 0 表示无心跳。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1504 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1551 msgid "" "Default: disable. When entering a negative number, such as -1, The Mux " "module will not be used to carry TCP traffic." msgstr "默认:禁用。填负数时,如 -1,不使用 Mux 模块承载 TCP 流量。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1517 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1564 msgid "" "Default:16. When entering a negative number, such as -1, The Mux module will " "not be used to carry UDP traffic, Use original UDP transmission method of " @@ -568,76 +676,72 @@ msgstr "" "默认值:16。填负数时,如 -1,不使用 Mux 模块承载 UDP 流量。将使用代理协议原本" "的 UDP 传输方式。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:184 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:212 -msgid "Defines the upstreams logic mode" -msgstr "定义上游逻辑模式" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:297 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:41 +msgid "Defaults Cleared!" +msgstr "已清空默认状态!" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:187 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:215 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:297 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:41 +msgid "Defaults Restored!" +msgstr "默认值已恢复!" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1027 msgid "" -"Defines the upstreams logic mode, possible values: load_balance, parallel, " -"fastest_addr (default: load_balance)." +"Define per-client Mihomo proxy targets. Rules are injected as SRC-IP-CIDR " +"entries before the original Clash rules." msgstr "" -"定义上游逻辑模式,可选择值:负载均衡、并行查询、最快响应(默认值:负载均" -"衡)。" +"为每个客户端指定 Mihomo 代理目标。规则会以 SRC-IP-CIDR 形式注入到原始 Clash " +"规则之前。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:432 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:302 msgid "Delay (ms)" msgstr "延迟(ms)" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:738 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:589 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:248 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:261 msgid "Delete" msgstr "删除" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:315 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:504 msgid "Delete All Subscribe Servers" -msgstr "删除所有订阅服务器节点" +msgstr "删除所有订阅节点" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:116 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:130 +msgid "Delete failed:" +msgstr "删除失败:" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:112 msgid "Deny Domain List" msgstr "禁止连接的域名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:62 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:70 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:78 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:37 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:663 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:81 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:33 msgid "Disable" msgstr "停用" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:263 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:200 msgid "Disable ChinaDNS-NG" msgstr "直通模式(禁用 ChinaDNS-NG)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:152 -msgid "Disable IPv6 In MosDNS Query Mode (Shunt Mode)" -msgstr "禁止 MosDNS 返回 IPv6 记录 (分流模式)" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:163 +msgid "Disable IPv6 for Overseas FQDN" +msgstr "禁止海外域名返回 IPv6 记录" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:180 -msgid "Disable IPv6 in MOSDNS query mode" -msgstr "禁止 MOSDNS 返回 IPv6 记录" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:197 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:226 -msgid "Disable IPv6 query mode" -msgstr "禁止返回 IPv6 记录" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:664 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:694 msgid "Disable QUIC path MTU discovery" msgstr "禁用 QUIC 启用 MTU 探测" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:853 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:883 msgid "Disable SNI" msgstr "关闭 SNI 服务器名称指示" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:734 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:764 msgid "Disable TCP No_delay" msgstr "禁用 TCP 无延迟" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:149 -msgid "Dnsproxy Parse List" -msgstr "DNSPROXY 解析列表" - #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm:18 msgid "Do Reset" msgstr "执行重置" @@ -646,119 +750,114 @@ msgstr "执行重置" msgid "Do you want to restore the client to default settings?" msgstr "是否要恢复客户端默认配置?" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1663 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:249 -msgid "DoH" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:230 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:259 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:196 msgid "DoT upstream (Need use wolfssl version)" msgstr "DoT 上游(需使用 wolfssl 版本)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1653 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:238 -msgid "Domain DNS Resolve" -msgstr "域名 DNS 解析" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:419 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1680 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:265 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:289 msgid "Domain Strategy" msgstr "域名解析策略" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:262 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:353 +msgid "DomainSocket Path" +msgstr "域 Socket 路径" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:199 msgid "Domestic DNS Server" msgstr "国内 DNS 服务器" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1189 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1219 msgid "Downlink Capacity(Default:Mbps)" msgstr "下行链路容量(默认:Mbps)" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:739 -msgid "Drag to reorder" -msgstr "拖动以重排" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:377 +msgid "Download Source" +msgstr "下载源" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:864 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:43 +msgid "Download failed" +msgstr "下载失败" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:894 msgid "Dual-stack Listening Socket" msgstr "双栈 Socket 监听" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1367 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1414 msgid "ECH Config" msgstr "ECH 配置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1381 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1428 msgid "ECH Query Policy" msgstr "ECH 查询策略" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:981 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1011 msgid "Early Data Header Name" msgstr "前置数据标头" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:737 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:240 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:246 msgid "Edit" msgstr "编辑" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:259 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:69 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:307 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:40 msgid "Edit ShadowSocksR Server" msgstr "编辑服务器配置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:271 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:408 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:82 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:157 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:198 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:278 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:54 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:101 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:560 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1031 msgid "Enable" msgstr "启用" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:858 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:888 msgid "Enable 0-RTT QUIC handshake" msgstr "客户端启用 0-RTT QUIC 连接握手" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:482 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:474 msgid "Enable Authentication" msgstr "启用用户名/密码认证" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:63 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1712 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:80 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1701 msgid "Enable Auto Switch" msgstr "启用自动切换" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1362 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1398 msgid "Enable ECH(optional)" msgstr "启用 ECH (可选)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:640 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:657 msgid "Enable Lazy Mode" msgstr "启用懒狗模式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1389 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1436 msgid "Enable ML-DSA-65(optional)" msgstr "启用 ML-DSA-65 (可选)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1548 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1595 msgid "" "Enable Multipath TCP, need to be enabled in both server and client " "configuration." msgstr "启用 Multipath TCP,需在服务端和客户端配置中同时启用。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1484 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1531 msgid "Enable Mux.Cool" msgstr "启用 Mux.Cool" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:95 -msgid "Enable Netflix Mode" -msgstr "启用 Netflix 分流模式" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:634 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:651 msgid "Enable Obfuscation" msgstr "启用混淆功能" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:542 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:536 msgid "Enable Plugin" msgstr "启用插件" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:602 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:619 msgid "Enable Port Hopping" msgstr "启用端口跳跃" @@ -766,200 +865,258 @@ msgstr "启用端口跳跃" msgid "Enable Server" msgstr "启动服务端" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:615 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:632 msgid "Enable Transport Protocol Settings" msgstr "启用传输协议设置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:720 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:750 msgid "Enable V2 protocol." msgstr "开启 V2 协议。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:719 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:749 msgid "Enable V3 protocol." msgstr "开启 V3 协议。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:249 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:134 msgid "Enable adblock" msgstr "启用广告屏蔽" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:531 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:525 msgid "Enable the SUoT protocol, requires server support." msgstr "启用 SUoT 协议,需要服务端支持。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1024 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1054 msgid "Enable this option to configure XHTTP Extra (JSON format)." msgstr "启用此选项配置 XHTTP 附加项(JSON 格式)。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1201 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1231 msgid "Enabled Kernel virtual NIC TUN(optional)" msgstr "启用内核的虚拟网卡 TUN(可选)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:335 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:181 msgid "Enabled Mixed" msgstr "启用 Mixed" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:729 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1643 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:759 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1690 msgid "Enabling TCP Fast Open Requires Server Support." msgstr "启用 TCP 快速打开需要服务端支持。" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:503 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:510 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:517 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:761 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:772 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:903 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:118 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:125 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:122 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:791 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:802 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:933 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:103 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:108 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:171 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:127 msgid "Encrypt Method" msgstr "加密方式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:122 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:115 msgid "Enter Custom Ports" msgstr "输入自定义端口" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:122 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:419 msgid "Every Day" msgstr "每天" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:127 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:424 msgid "Every Friday" msgstr "每周五" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:123 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:420 msgid "Every Monday" msgstr "每周一" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:128 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:425 msgid "Every Saturday" msgstr "每周六" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:129 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:426 msgid "Every Sunday" msgstr "每周日" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:126 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:423 msgid "Every Thursday" msgstr "每周四" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:124 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:421 msgid "Every Tuesday" msgstr "每周二" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:125 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:422 msgid "Every Wednesday" msgstr "每周三" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:289 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:606 +msgid "Example:" +msgstr "示例:" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:223 msgid "Expecting: %s" msgstr "应为:%s" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:86 -msgid "External Proxy Mode" -msgstr "分流服务器(前置)代理" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1019 +msgid "Export Client Rules" +msgstr "批量导出客户端规则" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1587 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:44 +msgid "Extract failed" +msgstr "解压失败" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1634 msgid "FORCE BRUTAL" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:282 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:967 +msgid "Failed to clear all client proxy rules." +msgstr "清空所有客户端代理规则失败。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:42 +msgid "Failed to create temp directory" +msgstr "创建临时目录失败" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:230 +msgid "Failed to create temporary YAML upload file." +msgstr "创建临时 YAML 上传文件失败。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:37 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:77 +msgid "Failed to fetch release metadata" +msgstr "获取发布信息失败" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:99 +msgid "Failed to query component information." +msgstr "获取组件信息失败。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:210 +msgid "Failed to query geo database information." +msgstr "获取 Geo 数据库信息失败。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:710 +msgid "Failed to save custom client proxy rules." +msgstr "保存自定义客户端代理规则失败。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:219 +msgid "File Not Exist" +msgstr "文件不存在" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:461 msgid "Filter Words splited by /" msgstr "命中关键字的节点将被丢弃。多个关键字用 / 分隔" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1260 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1290 msgid "FinalMask" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1346 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1382 msgid "Finger Print" msgstr "指纹伪造" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1333 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1369 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:212 msgid "Flow" msgstr "流控(Flow)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:234 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:119 msgid "" "For Apple domains equipped with Chinese mainland CDN, always responsive to " "Chinese CDN IP addresses" msgstr "配备中国大陆 CDN 的 Apple 域名,始终应答中国大陆 CDN 地址" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:392 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:262 msgid "For specific usage, see:" msgstr "具体使用方法,具体请参见:" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:609 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:626 msgid "" "Format as 10000:20000 or 10000-20000 Multiple groups are separated by commas " "(,)." msgstr "格式为:10000:20000 或 10000-20000 多组时用逗号(,)隔开。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:88 -msgid "Forward Netflix Proxy through Main Proxy" -msgstr "分流服务器流量通过主服务节点中转代理转发" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:358 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:228 msgid "Fragment" msgstr "分片" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:375 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:245 msgid "Fragment Delay" msgstr "分片延迟" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:370 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:240 msgid "Fragment Length" msgstr "分片包长" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:361 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:231 msgid "Fragment Packets" msgstr "分片方式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:375 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:245 msgid "Fragmentation interval (ms)" msgstr "分片间隔(ms)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:370 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:240 msgid "Fragmented packet length (byte)" msgstr "分片包长 (byte)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:197 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:256 msgid "GFW List Data" msgstr "【GFW 列表】数据库" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:112 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:106 msgid "GFW List Mode" msgstr "GFW 列表模式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:68 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:64 msgid "Game Mode Host List" msgstr "增强游戏模式客户端 LAN IP" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:134 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:183 msgid "Game Mode UDP Relay" msgstr "游戏模式 UDP 中继" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:69 -msgid "Game Mode UDP Server" -msgstr "游戏模式 UDP 中继服务器" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:829 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:859 msgid "Garbage collection interval(second)" msgstr "UDP 数据包片残片清理间隔(单位:秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:835 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:865 msgid "Garbage collection lifetime(second)" msgstr "UDP 数据包残片在服务器的保留时间(单位:秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:126 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:416 +msgid "Geo Database Update" +msgstr "Geo 数据库更新" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:425 +msgid "Geo Resource" +msgstr "Geo 资源" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:24 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:429 +msgid "GeoSite Database" +msgstr "GeoSite 数据库" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua:14 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:381 +msgid "GitHub Direct" +msgstr "GitHub 直连" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:175 msgid "Global Client" msgstr "TCP 透明代理" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:114 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:194 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:210 +msgid "Global HTTP/HTTPS Proxy Server" +msgstr "HTTP/HTTPS 代理服务端(全局)" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:108 msgid "Global Mode" msgstr "全局模式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:267 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:152 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:152 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:201 msgid "Global SOCKS5 Proxy Server" msgstr "SOCKS5 代理服务端(全局)" @@ -967,31 +1124,25 @@ msgstr "SOCKS5 代理服务端(全局)" msgid "Global Setting" msgstr "全局设置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:189 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:76 +msgid "Go to relevant configuration page" +msgstr "前往相关配置页面" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:248 msgid "Google Connectivity" msgstr "【谷歌】连通性检查" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:174 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:202 -msgid "Google DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:143 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:171 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:153 msgid "Google Public DNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:126 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:207 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:150 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:236 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:135 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:172 msgid "Google Public DNS (8.8.4.4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:127 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:208 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:151 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:237 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:136 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:173 msgid "Google Public DNS (8.8.8.8)" msgstr "" @@ -999,330 +1150,414 @@ msgstr "" msgid "Grant UCI access for luci-app-ssr-plus" msgstr "授予访问 luci-app-ssr-plus 配置的权限" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1095 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1125 msgid "Gun" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1114 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1144 msgid "H2 Read Idle Timeout" msgstr "H2 读取空闲超时" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1109 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1139 msgid "H2/gRPC Health Check" msgstr "H2/gRPC 健康检查" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:453 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:942 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:421 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:972 msgid "HTTP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:946 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:202 +msgid "HTTP Auth Mode" +msgstr "HTTP 认证模式" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:976 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:291 msgid "HTTP Host" msgstr "HTTP 主机名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:951 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:212 +msgid "HTTP Password" +msgstr "HTTP 密码" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:981 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:295 msgid "HTTP Path" msgstr "HTTP 路径" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1077 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:208 +msgid "HTTP User" +msgstr "HTTP 用户名" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:202 +msgid "HTTP proxy auth method, default:none." +msgstr "HTTP 代理认证方式,默认:none。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1107 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:268 msgid "HTTP/2 Host" msgstr "HTTP/2 主机名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1082 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1112 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:274 msgid "HTTP/2 Path" msgstr "HTTP/2 路径" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1149 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1179 msgid "Header" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1126 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1156 msgid "Health Check Timeout" msgstr "健康检查超时" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:817 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:847 msgid "Heartbeat interval(second)" msgstr "保活心跳包发送间隔(单位:秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:968 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:998 msgid "HeartbeatPeriod(second)" msgstr "心跳周期(单位:秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:990 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm:88 +msgid "Hour" +msgstr "时" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1020 msgid "Httpupgrade Host" msgstr "HTTPUpgrade 主机名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:995 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1025 msgid "Httpupgrade Path" msgstr "HTTPUpgrade 路径" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:284 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:357 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:450 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:84 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:407 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:418 msgid "Hysteria2" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:658 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:598 +msgid "Hysteria2 Realms" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:688 msgid "Hysterir QUIC parameters" msgstr "QUIC 参数" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:113 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:107 msgid "IP Route Mode" msgstr "绕过中国大陆 IP 模式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1443 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1490 msgid "IP Stack Preference" msgstr "IP 栈优先级" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1691 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:277 -msgid "IPv4 Only" -msgstr "仅 IPv4" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1692 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:278 -msgid "IPv6 Only" -msgstr "仅 IPv6" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:243 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:128 msgid "If empty, Not change Apple domains parsing DNS (Default is empty)" msgstr "如果为空,则不更改 Apple 域名解析 DNS(默认为空)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1683 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:268 -msgid "" -"If is domain name, The requested domain name will be resolved to IP before " -"connect." -msgstr "如果是域名,域名将在请求发出之前解析为 IP。" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1369 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1416 msgid "" "If it is not empty, it indicates that the Client has enabled Encrypted " "Client, see:" msgstr "如果不为空,表示客户端已启用加密客户端,具体请参见:" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1656 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:241 -msgid "" -"If the node address is a domain name, this DNS will be used for resolution." -msgstr "如果节点地址是域名,则将使用此 DNS 进行解析。" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:865 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:895 msgid "If this option is not set, the socket behavior is platform dependent." msgstr "如果未设置此选项,则 Socket 行为依赖于平台。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1456 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1503 msgid "" "If true, allowss insecure connection at TLS client, e.g., TLS server uses " "unverifiable certificates." msgstr "" "是否允许不安全连接。当选择时,将不会检查远端主机所提供的 TLS 证书的有效性。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1601 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1648 msgid "If you have a self-signed certificate,please check the box" msgstr "如果你使用自签证书,请选择" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1193 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1088 msgid "Import" msgstr "导入配置信息" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:249 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:425 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:597 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:672 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:706 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:813 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:952 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1104 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1184 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1020 +msgid "Import Client Rules" +msgstr "批量导入客户端规则" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:195 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:376 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:520 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:590 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:624 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:731 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:847 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:999 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1079 msgid "Import configuration information successfully." msgstr "导入配置信息成功。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1102 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1132 msgid "Initial Windows Size" msgstr "初始窗口大小" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:17 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:48 +msgid "Install failed" +msgstr "安装失败" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:108 +msgid "Installed Version" +msgstr "已安装版本" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:49 +msgid "Installed binary failed to run" +msgstr "新安装的二进制无法运行" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:13 msgid "Interface" msgstr "接口" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:16 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:12 msgid "Interface control" msgstr "接口控制" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1187 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:293 +msgid "Invalid" +msgstr "无效的" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1082 msgid "Invalid format." msgstr "无效的格式。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:172 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:330 +msgid "KCP Congestion Control" +msgstr "KCP 拥塞控制" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:324 +msgid "KCP Downlink Capacity" +msgstr "KCP 下行带宽容量" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:306 +msgid "KCP MTU" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:335 +msgid "KCP Read Buffer Size" +msgstr "KCP 读缓冲区大小" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:347 +msgid "KCP Seed" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:312 +msgid "KCP TTI" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:318 +msgid "KCP Uplink Capacity" +msgstr "KCP 上行链路容量" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:341 +msgid "KCP Write Buffer Size" +msgstr "KCP 写缓冲区大小" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:231 msgid "KcpTun" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1722 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1706 msgid "KcpTun Enable" msgstr "KcpTun 启用" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1739 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1726 msgid "KcpTun Param" msgstr "KcpTun 参数" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1734 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1720 msgid "KcpTun Password" msgstr "KcpTun 密码" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1728 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1713 msgid "KcpTun Port" msgstr "KcpTun 端口" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:169 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:228 msgid "KcpTun Version" msgstr "KcpTun 版本号" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:36 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:32 msgid "LAN Access Control" msgstr "内网客户端分流代理控制" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:52 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:48 msgid "LAN Bypassed Host List" msgstr "不走代理的局域网 LAN IP" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:60 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:56 msgid "LAN Force Proxy Host List" msgstr "全局代理的 LAN IP" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:42 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:38 msgid "LAN Host List" msgstr "内网主机列表" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:34 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:30 msgid "LAN IP AC" msgstr "LAN IP 访问控制" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:130 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:211 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:154 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:240 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:109 +msgid "Latest Version" +msgstr "最新版本" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:139 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:176 msgid "Level 3 Public DNS (209.244.0.3)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:131 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:212 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:155 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:241 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:140 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:177 msgid "Level 3 Public DNS (209.244.0.4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:132 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:213 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:156 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:242 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:141 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:178 msgid "Level 3 Public DNS (4.2.2.1)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:133 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:214 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:157 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:243 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:142 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:179 msgid "Level 3 Public DNS (4.2.2.2)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:134 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:215 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:158 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:244 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:143 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:180 msgid "Level 3 Public DNS (4.2.2.3)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:135 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:216 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:159 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:245 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:144 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:181 msgid "Level 3 Public DNS (4.2.2.4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:145 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:173 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:155 msgid "Level 3 Public DNS-1 (209.244.0.3-4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:146 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:174 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:156 msgid "Level 3 Public DNS-2 (4.2.2.1-2)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:147 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:175 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:157 msgid "Level 3 Public DNS-3 (4.2.2.3-4)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:380 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:250 msgid "Limit the maximum number of splits." msgstr "限制分片的最大数量。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1204 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1234 msgid "" "Linux kernel TUN virtual NIC requires system support and root privileges." msgstr "Linux 内核 TUN 虚拟网卡需要系统支持和 root 权限。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:22 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:18 msgid "Listen only on the given interface or, if unspecified, on all" msgstr "仅监听指定的接口,未指定则监听全部。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:348 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1716 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:718 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:771 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:84 +msgid "Loading..." +msgstr "加载中..." + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:187 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:217 msgid "Local Port" msgstr "本地端口" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:160 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:219 msgid "Local Servers" msgstr "本机服务端" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1212 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1242 msgid "Local addresses" msgstr "本地地址" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:66 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:643 msgid "Log" msgstr "日志" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:85 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:413 +msgid "Loop Mode" +msgstr "循环模式" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:109 msgid "Loukky/gfwlist-by-loukky" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:84 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:108 msgid "Loyalsoldier/v2ray-rules-dat" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1394 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1441 msgid "ML-DSA-65 Public key" msgstr "ML-DSA-65 公钥" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1548 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1595 msgid "MPTCP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1175 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1205 msgid "MTU" msgstr "最大传输单元" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:61 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:80 msgid "Main Server" msgstr "主服务器" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:974 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:558 +msgid "" +"Manage multiple subscribe URLs, including Clash subscriptions. Only enabled " +"entries are included when updating all subscriptions." +msgstr "" +"管理多条订阅 URL,包括 Clash 订阅。只有启用的订阅项才会参与“更新所有订阅节" +"点”。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:38 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:78 +msgid "Matching release asset not found" +msgstr "未找到匹配的发布资产" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1004 msgid "Max Early Data" msgstr "最大前置数据" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:380 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:250 msgid "Max Split" msgstr "最大分片数" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:870 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:900 msgid "Maximum packet size the socks5 server can receive from external" msgstr "socks5 服务器可以从外部接收的最大数据包大小(单位:字节)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1518 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:372 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:375 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:378 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:9 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:395 +msgid "Mihomo" +msgstr "Mihomo" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1005 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1006 +msgid "Mihomo Pannel" +msgstr "Mihomo 面板" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_schedule_compact.htm:89 +msgid "Min" +msgstr "分" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1565 msgid "" "Min value is 1, Max value is 1024. When omitted or set to 0, Will same path " "as TCP traffic." @@ -1330,154 +1565,174 @@ msgstr "" "最小值 1,最大值 1024。 省略或者填 0 时,将与 TCP 流量走同一条路,也就是传统" "的行为。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1505 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1552 msgid "" "Min value is 1, Max value is 128. When omitted or set to 0, it equals 8." msgstr "最小值 1,最大值 128。省略或者填 0 时都等于 8。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:335 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/component.lua:13 +msgid "Mirror URL" +msgstr "镜像地址" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:40 +msgid "Missing gzip support" +msgstr "缺少 gzip 支持" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:39 +msgid "Missing unzip support" +msgstr "缺少 unzip 支持" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:41 +msgid "Missing xz support" +msgstr "缺少 xz 支持" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:181 msgid "Mixed as an alias of socks, default:Enabled." msgstr "Mixed 作为 SOCKS 的别名,默认:启用。" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/optimize_cbi_ui.htm:10 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:735 msgid "Move down" msgstr "下移" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/optimize_cbi_ui.htm:7 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:734 msgid "Move up" msgstr "上移" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:222 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:251 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:188 msgid "Muitiple DNS server can saperate with ','" msgstr "多个上游 DNS 服务器请用 ',' 分隔(注意用英文逗号)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1096 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1126 msgid "Multi" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:98 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:92 msgid "Multi Threads Option" msgstr "多线程并发转发" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1066 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1290 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1096 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1326 msgid "Must be JSON text!" msgstr "必须是 JSON 文本内容!" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1484 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1531 msgid "Mux" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:253 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:138 msgid "NEO DEV HOST" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:10 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:11 msgid "NOT RUNNING" msgstr "未运行" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:354 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:401 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:349 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:13 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:396 msgid "NaiveProxy" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:266 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:27 +msgid "Name" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:203 msgid "Nanjing Xinfeng 114DNS (114.114.114.114)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:132 -msgid "Netflix Domain List" -msgstr "Netflix 分流域名列表" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:215 -msgid "Netflix IP Data" -msgstr "【Netflix IP 段】数据库" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:100 -msgid "Netflix IP Only" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:77 -msgid "Netflix Node" -msgstr "Netflix 分流服务器" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:101 -msgid "Netflix and AWS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:369 -msgid "Network Tunnel" -msgstr "网络隧道" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:399 -msgid "Network interface to use" -msgstr "使用的网络接口" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:813 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:843 msgid "New Reno" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:190 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:194 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:230 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:149 +msgid "Next" +msgstr "下一页" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:249 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:253 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:282 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:116 msgid "No Check" msgstr "未检查" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:209 -msgid "No available core (Hysteria2 or Xray) to import this node." -msgstr "没有可用的核心(Hysteria2 或 Xray)可以导入此节点。" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:156 +msgid "No available Xray core to import this Hysteria2 node." +msgstr "没有可用的 Xray 核心可以导入此 Hysteria2 节点。" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:632 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:546 msgid "No available core (Shadowsocks or Xray) to import this node." msgstr "没有可用的核心(Shadowsocks 或 Xray)可以导入此节点。" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:842 -msgid "No available core (Trojan or Xray) to import this node." -msgstr "没有可用的核心(Trojan 或 Xray)可以导入此节点。" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:738 +msgid "No available core (Xray) to import this node." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:249 +msgid "No custom YAML file was selected." +msgstr "没有选择自定义 YAML 文件。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:474 +msgid "No custom client proxy rules yet." +msgstr "暂无自定义客户端代理规则。" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm:21 msgid "No new data!" msgstr "你已经是最新数据,无需更新!" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1633 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1680 msgid "No specify upload file." msgstr "没有上传证书。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:385 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:94 +msgid "No subscription items found" +msgstr "没有找到任何订阅项" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:615 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:32 +msgid "No switchable proxy groups found." +msgstr "未找到可切换的代理组。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:582 +msgid "" +"Node order can be dragged with the mouse and takes effect immediately. The " +"automatic switch order of server nodes is consistent with the node order in " +"the table." +msgstr "" +"节点顺序可用鼠标拖拉后立即生效,服务器节点自动切换顺序和表中节点顺序一致。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:255 msgid "Noise" msgstr "噪声" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:549 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:941 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1140 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1152 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1162 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:364 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:369 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:544 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:971 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1170 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1182 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1192 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:677 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:682 msgid "None" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:131 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:139 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:148 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:157 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:165 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:177 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:180 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:188 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:197 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:206 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:215 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:224 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:236 msgid "Not Running" msgstr "未运行" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:28 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:35 msgid "Not exist" msgstr "未安装可执行文件" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1657 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1684 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:243 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:270 -msgid "Note: For node-specific DNS only. Keep Auto to avoid extra overhead." -msgstr "注意:仅用于节点专用 DNS,通常请保持自动,以免增加开销。" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:108 +msgid "Not installed" +msgstr "未安装" #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/log.lua:27 msgid "" @@ -1485,116 +1740,120 @@ msgid "" "compatibility issues." msgstr "注意:不同版本间的配置恢复可能会导致兼容性问题。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1559 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1606 msgid "Number of early established connections to reduce latency." msgstr "预连接的数量,用于降低延迟。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:548 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:584 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:139 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:133 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:543 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:586 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:118 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:138 msgid "Obfs" msgstr "混淆插件" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:591 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:146 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:593 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:123 msgid "Obfs param (optional)" msgstr "混淆参数(可选)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1196 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1226 msgid "Obfuscate password (optional)" msgstr "混淆密码(可选)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:651 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:670 msgid "Obfuscation Password" msgstr "混淆密码" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:645 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:662 msgid "Obfuscation Type" msgstr "混淆类型" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1468 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1515 msgid "Once set, connects only when the server’s chain fingerprint matches." msgstr "设置后,仅在服务器证书链指纹匹配时连接。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:120 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:113 msgid "Only Common Ports" msgstr "仅常用端口(不走 P2P 流量到代理)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:324 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:208 +msgid "Only when HTTP Auth Mode is password valid, Mandatory." +msgstr "仅当 HTTP 认证模式为 password 时有效,必填。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:212 +msgid "Only when HTTP Auth Mode is password valid, Not mandatory." +msgstr "仅当 HTTP 认证模式为 password 时有效,非必填。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:170 msgid "Only when Socks5 Auth Mode is password valid, Mandatory." msgstr "仅当 Socks5 认证方式为 Password 时有效,必填。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:329 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:175 msgid "Only when Socks5 Auth Mode is password valid, Not mandatory." msgstr "仅当 Socks5 认证方式为 Password 时有效,非必填。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:144 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:172 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:154 msgid "OpenDNS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:129 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:210 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:153 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:239 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:138 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:175 msgid "OpenDNS (208.67.220.220)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:128 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:209 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:152 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:238 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:137 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:174 msgid "OpenDNS (208.67.222.222)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:115 -msgid "Oversea Mode" -msgstr "海外用户回国模式" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:391 +msgid "Package Name" +msgstr "组件包名称" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:161 -msgid "Oversea Mode DNS-1 (114.114.114.114)" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:162 -msgid "Oversea Mode DNS-2 (114.114.115.115)" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:391 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:261 msgid "Packet or Rand length as a string, e.g., 10-20." msgstr "数据包或 Rand 长度以字符串形式输入,例如:10-20。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:428 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:298 msgid "Packet | Rand Length" msgstr "数据包 | Rand 长度" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:496 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:114 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:68 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:69 +msgid "Panel" +msgstr "面板" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:488 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:93 msgid "Password" msgstr "密码" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:105 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:102 msgid "Paste sharing link here" msgstr "在此处粘贴分享链接" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1227 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1257 msgid "Peer public key" msgstr "节点公钥" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:154 +msgid "Per page" +msgstr "每页" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:14 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:23 msgid "Perform reset" msgstr "执行重置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1132 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1162 msgid "Permit Without Stream" msgstr "允许无数据流" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:393 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:725 msgid "Ping Latency" msgstr "Ping 延迟" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1640 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1687 msgid "Please confirm the current certificate path" msgstr "请选择确认所传证书,证书不正确将无法运行" @@ -1602,96 +1861,108 @@ msgstr "请选择确认所传证书,证书不正确将无法运行" msgid "Please fill in reset" msgstr "请填写 reset" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:570 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:387 +msgid "Please specify either a Clash subscription URL or a local YAML path." +msgstr "请填写 Clash 订阅 URL 或本地 YAML 路径中的至少一项。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:572 msgid "Plugin Opts" msgstr "插件参数" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:626 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1033 +msgid "Policy" +msgstr "策略" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:643 msgid "Port Hopping Interval(Unit:Second)" msgstr "端口跳跃间隔(单位:秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:608 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:625 msgid "Port hopping range" msgstr "端口跳跃范围" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1559 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1606 msgid "Pre-connections" msgstr "预连接" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1231 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1261 msgid "Pre-shared key" msgstr "预共享密钥" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1689 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:275 -msgid "Prefer IPv4" -msgstr "IPv4 优先" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:130 +msgid "Prefer module built-in DNS" +msgstr "优先使用模块内置 DNS" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1690 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:276 -msgid "Prefer IPv6" -msgstr "IPv6 优先" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:139 +msgid "Prev" +msgstr "上一页" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:93 -msgid "Prefer firewall tools" -msgstr "首选防火墙工具" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:232 +msgid "Private Key File Path" +msgstr "私钥文件路径" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1222 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1252 msgid "Private key" msgstr "私钥" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:574 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:132 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:128 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:150 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:254 +msgid "Processing..." +msgstr "处理中..." + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:576 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:113 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:133 msgid "Protocol" msgstr "传输协议" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:581 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:583 msgid "Protocol param (optional)" msgstr "传输协议参数(可选)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:118 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:111 msgid "Proxy Ports" msgstr "需要代理的端口" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1320 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1356 msgid "Public key" msgstr "公钥" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1145 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:371 +msgid "QUIC Camouflage Type" +msgstr "QUIC 伪装(混淆)类型" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1175 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:367 msgid "QUIC Key" msgstr "QUIC 密钥" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1138 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1168 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:359 msgid "QUIC Security" msgstr "QUIC 加密方式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:685 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:715 msgid "QUIC initConnReceiveWindow" msgstr "QUIC 初始的连接接收窗口大小" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:671 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:701 msgid "QUIC initStreamReceiveWindow" msgstr "QUIC 初始流接收窗口大小。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:692 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:722 msgid "QUIC maxConnReceiveWindow" msgstr "QUIC 最大的连接接收窗口大小" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:699 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:729 msgid "QUIC maxIdleTimeout(Unit:second)" msgstr "QUIC 最长空闲超时时间(单位:秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:678 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:708 msgid "QUIC maxStreamReceiveWindow" msgstr "QUIC 最大的流接收窗口大小" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:177 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:205 -msgid "Quad9 DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1315 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1351 msgid "REALITY" msgstr "" @@ -1699,33 +1970,59 @@ msgstr "" msgid "RST Backup" msgstr "恢复备份" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:429 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/status.htm:7 msgid "RUNNING" msgstr "运行中" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:422 +msgid "RUNNING in %s (%s) Mode" +msgstr "运行中,当前为 %s(%s)模式" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:426 +msgid "RUNNING in %s Mode" +msgstr "运行中,当前为 %s 模式" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:604 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:752 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:802 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:93 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:228 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:253 +msgid "Ready." +msgstr "就绪。" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:5 msgid "Really reset all changes?" msgstr "真的重置所有更改吗?" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:403 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:609 +msgid "Realm STUN" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:606 +msgid "Realm URL" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:744 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:272 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:17 msgid "Reapply" msgstr "重新应用" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:200 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:205 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:211 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:218 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:225 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:259 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:264 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:270 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:277 msgid "Records" msgstr "条记录" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:406 -msgid "Redirect traffic to this network interface" -msgstr "分流到这个网络接口" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:10 +msgid "Refresh" +msgstr "更新" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm:29 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm:35 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:11 msgid "Refresh Data" msgstr "更新数据库" @@ -1738,45 +2035,90 @@ msgid "Refresh OK!" msgstr "更新成功!" #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm:6 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:5 msgid "Refresh..." msgstr "正在更新,请稍候..." -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:134 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:431 msgid "Regular update (Hour)" msgstr "定时更新(小时)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:142 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:439 msgid "Regular update (Min)" msgstr "定时更新(分钟)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1572 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1588 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:111 +msgid "Release Asset" +msgstr "发布包名" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:56 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:57 +msgid "Reload YAML" +msgstr "重载 YAML" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:265 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:10 +msgid "Reloading YAML..." +msgstr "正在重载 YAML..." + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:133 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:134 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1034 +msgid "Remarks" +msgstr "备注" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1619 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1635 msgid "Reno" +msgstr "Reno" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:154 +msgid "Required for VMess/VLESS. Generate with: uuidgen" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1217 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1247 msgid "Reserved bytes(optional)" msgstr "保留字节(可选)" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:998 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:999 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:62 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:63 +msgid "Reset Default Proxies Rules" +msgstr "重置代理分组规则" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:300 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:44 +msgid "Reset Defaults Failed!" +msgstr "重置默认值失败!" + #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:17 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:18 msgid "Reset complete" msgstr "重置完成" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:259 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:286 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:30 +msgid "Reset saved Mihomo proxy-group selections and restore YAML defaults?" +msgstr "要清空已保存的 Mihomo 分组选择,并恢复为 YAML 默认值吗?" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:144 msgid "Reset to defaults" msgstr "恢复出厂设置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:127 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:290 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:34 +msgid "Resetting Defaults..." +msgstr "正在重置默认值..." + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:120 msgid "Resolve Dns Mode" msgstr "DNS 解析方式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:182 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:241 msgid "Restart Service" msgstr "重启服务" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:181 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:240 msgid "Restart ShadowSocksR Plus+" msgstr "重启 ShadowSocksR Plus+" @@ -1789,92 +2131,93 @@ msgstr "恢复备份文件" msgid "Restore to default configuration" msgstr "恢复默认配置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:129 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:137 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:146 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:155 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:163 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:175 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:178 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:186 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:195 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:204 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:213 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:222 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:234 msgid "Running" msgstr "运行中" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:111 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:105 msgid "Running Mode" msgstr "运行模式" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:342 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:403 +msgid "SOCKS5" +msgstr "SOCKS5" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:301 msgid "SS URL base64 sstr format not recognized." msgstr "无法识别 SS URL 的 Base64 格式。" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:56 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:625 msgid "SSR Client" msgstr "客户端" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:60 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:629 msgid "SSR Server" msgstr "服务端" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:277 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:71 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:79 -msgid "Same as Global Server" -msgstr "与全局服务器相同" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:1023 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:260 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:285 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:286 +msgid "Save" +msgstr "保存" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:750 -msgid "Save Order" -msgstr "保存当前顺序" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:492 +msgid "Save Subscribe Settings" +msgstr "保存订阅设置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:286 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:467 msgid "Save Words splited by /" msgstr "" "命中关键字的节点将被保留。多个关键字用 / 分隔。此项为空则不启用保留匹配" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:484 -msgid "Save failed!" -msgstr "保存失败!" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:494 +msgid "Save current subscribe settings" +msgstr "保存当前订阅设置" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:482 -msgid "Saved current page order successfully." -msgstr "保存当前页面顺序成功。" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:82 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:105 +msgid "Save failed:" +msgstr "保存失败:" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:747 -msgid "Saving the new order..." -msgstr "正在保存新的顺序…" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:687 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:51 +msgid "Saving..." +msgstr "保存中..." -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:158 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:186 -msgid "Select DNS parse Mode" -msgstr "选择 DNS 解析方式" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm:77 +msgid "Second" +msgstr "周期" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:410 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:228 -msgid "Selection ShadowSocks Node Use Version." -msgstr "选择 ShadowSocks 节点使用版本。" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1593 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1640 msgid "Self-signed Certificate" msgstr "自签证书" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:276 -msgid "Server" -msgstr "服务器" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:456 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:424 msgid "Server Address" msgstr "服务器地址" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:317 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:506 msgid "Server Count" msgstr "服务器节点数量" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:340 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:223 +msgid "Server Name (SNI)" +msgstr "服务器名称(SNI)" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:335 msgid "Server Node Type" msgstr "服务器节点类型" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:469 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:96 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:112 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:372 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:449 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:75 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:117 msgid "Server Port" msgstr "端口" @@ -1882,258 +2225,293 @@ msgstr "端口" msgid "Server Setting" msgstr "服务端配置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:86 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:58 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:107 msgid "Server Type" msgstr "服务端类型" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:56 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:73 msgid "Server failsafe auto swith and custom update settings" msgstr "服务器节点故障自动切换/广告屏蔽/中国大陆 IP 段数据库更新设置" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:57 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:626 msgid "Servers Nodes" msgstr "服务器节点" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:111 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:393 msgid "Servers subscription and manage" msgstr "服务器节点订阅与管理" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1309 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1345 msgid "Session Ticket" msgstr "会话凭据" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:167 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:195 -msgid "Set Single DNS" -msgstr "设置单个 DNS" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:363 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:358 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:558 msgid "Shadow-TLS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:746 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:776 msgid "Shadow-TLS ChainPoxy type" msgstr "代理链类型" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:348 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:445 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:89 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:343 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:346 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:413 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:61 msgid "ShadowSocks" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:415 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:101 -msgid "ShadowSocks-libev Version" -msgstr "ShadowSocks-libev 版本" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:412 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:749 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:98 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:779 msgid "ShadowSocks-rust Version" msgstr "ShadowSocks-rust 版本" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:53 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:622 msgid "ShadowSocksR Plus+" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:38 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:51 msgid "ShadowSocksR Plus+ Settings" msgstr "ShadowSocksR Plus+ 设置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:757 -msgid "Shadowsocks password" -msgstr "shadowsocks密码" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:405 +msgid "ShadowTLS" +msgstr "ShadowTLS" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:345 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:92 -msgid "ShadowsocksR" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:373 +msgid "Shadowsocks" +msgstr "Shadowsocks" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:787 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:166 +msgid "Shadowsocks Password" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1324 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:383 +msgid "Shadowsocks-rust" +msgstr "Shadowsocks-rust" + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:381 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:340 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:64 +msgid "ShadowsocksR" +msgstr "ShadowsocksR" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1360 msgid "Short ID" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:377 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:134 +msgid "Showing %d-%d of %d nodes" +msgstr "当前显示第 %d-%d 个节点,共 %d 个" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:685 msgid "Socket Connected" msgstr "连接测试" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:452 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:420 msgid "Socks" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:911 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:941 msgid "Socks Version" msgstr "Socks 版本" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:310 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:163 msgid "Socks protocol auth methods, default:noauth." msgstr "Socks 协议的认证方式,默认值:noauth。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:366 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:87 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:361 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:59 msgid "Socks5" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:310 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:163 msgid "Socks5 Auth Mode" msgstr "Socks5 认证方式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:329 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:175 msgid "Socks5 Password" msgstr "Socks5 密码" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:324 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:170 msgid "Socks5 User" msgstr "Socks5 用户名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:151 -msgid "Specifically for edit dnsproxy DNS parse files." -msgstr "专门用于编辑 DNSPROXY 的 DNS 解析文件。" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:73 +msgid "Start Detection" +msgstr "开始探测" -#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:61 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:205 +msgid "Starting subscription update..." +msgstr "正在开始更新订阅..." + +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:631 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:114 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:116 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:118 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:120 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:224 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:226 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:228 msgid "Status" msgstr "状态" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:302 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:74 +msgid "Stop Detection" +msgstr "停止探测" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:453 +msgid "Subscribe Advanced Settings" +msgstr "订阅高级设置" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:478 msgid "Subscribe Default Auto-Switch" msgstr "订阅新节点自动切换设置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:280 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:459 msgid "Subscribe Filter Words" msgstr "订阅节点关键字过滤" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:284 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:250 +msgid "Subscribe Log" +msgstr "订阅日志" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:465 msgid "Subscribe Save Words" msgstr "订阅节点关键字保留检查" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:235 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:551 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:575 msgid "Subscribe URL" -msgstr "SS/SSR/V2/TROJAN/HY2/TUIC 订阅 URL" +msgstr "订阅 URL (支持SS/SSR/V2/TROJAN/HY2/TUIC/CLASH等)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:304 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:194 +msgid "Subscribe failed, refreshing page..." +msgstr "订阅失败,正在刷新页面..." + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:480 msgid "Subscribe new add server default Auto-Switch on" msgstr "订阅加入的新节点默认开启自动切换" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:299 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:473 msgid "Subscribe nodes allows insecure connection as TLS client (insecure)" msgstr "订阅节点强制开启 不验证TLS客户端证书 (insecure)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:257 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:142 msgid "Support AdGuardHome and DNSMASQ format list" msgstr "同时支持 AdGuard Home 和 DNSMASQ 格式的过滤列表" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:627 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:644 msgid "Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5." msgstr "支持固定值或随机范围(如 30 或 5-30),最小 5 秒。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:242 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:269 -msgid "Supports only Xray node types." -msgstr "仅支持 Xray 类型节点。" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1248 +msgid "" +"Supports decimal numbers separated by \",\" or Base64-encoded strings, with " +"a maximum length of 3 bytes." +msgstr "" +"支持以“,”分隔的十进制数字,或者经过 Base64 编码的字符串,其最大长度为 3 个字" +"节。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:67 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:84 msgid "Switch check cycly(second)" msgstr "自动切换检查周期(秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1661 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:247 -msgid "TCP" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:654 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:71 +msgid "Switch failed." +msgstr "切换失败。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:651 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:68 +msgid "Switched successfully." +msgstr "切换成功。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:644 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:61 +msgid "Switching..." +msgstr "正在切换..." + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:282 +msgid "TCP Camouflage Type" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:729 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1643 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:149 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:759 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1690 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:126 msgid "TCP Fast Open" msgstr "TCP 快速打开" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:358 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:228 msgid "" "TCP fragments, which can deceive the censorship system in some cases, such " "as bypassing SNI blacklists." msgstr "TCP 分片,在某些情况下可以欺骗审查系统,比如绕过 SNI 黑名单。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:228 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:257 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:194 msgid "TCP upstream" msgstr "TCP 上游" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1295 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1331 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:199 msgid "TLS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:724 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:754 msgid "TLS 1.3 Strict mode" msgstr "TLS 1.3 限定模式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1417 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1464 msgid "TLS ALPN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1473 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1520 msgid "TLS Certificate Name (CertName)" msgstr "TLS 证书名称(CertName)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1468 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1515 msgid "TLS Chain Fingerprint (SHA256)" msgstr "TLS 证书链指纹(SHA256)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1409 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1456 msgid "TLS Host" msgstr "TLS 主机名" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:539 -msgid "TLS handshake test, latency for reference only" -msgstr "TLS握手测试,延时仅供参考" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1473 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1520 msgid "TLS is used to verify the leaf certificate name." msgstr "TLS 用于验证 leaf 证书的 name。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:360 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:379 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:355 msgid "TUIC" -msgstr "" +msgstr "TUIC" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1429 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1476 msgid "TUIC ALPN" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:789 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:819 msgid "TUIC Server IP Address" msgstr "TUIC 服务器 IP 地址" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:796 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:826 msgid "TUIC User Password" msgstr "TUIC 用户密钥" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:782 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:812 msgid "TUIC User UUID" msgstr "TUIC 用户 uuid" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:847 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:877 msgid "TUIC receive window" msgstr "接收窗口(无需确认即可接收的最大字节数:默认8Mb)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:841 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:871 msgid "TUIC send window" msgstr "发送窗口(无需确认即可发送的最大字节数:默认8Mb*2)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:175 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:203 -msgid "TWNIC-101 DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:539 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:604 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:628 -msgid "Test" -msgstr "测试" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:581 -msgid "Testing..." -msgstr "检测中…" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1396 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1443 msgid "" "The client has not configured mldsa65Verify, but it will not perform the " "\"additional verification\" step and can still connect normally, see:" @@ -2141,49 +2519,74 @@ msgstr "" "客户端若未配置 mldsa65Verify,但它不会执行 \"附加验证\" 步骤,仍可以正常连" "接,具体请参见:" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:280 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:311 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:171 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:199 -msgid "" -"The configured type also applies to the core specified when manually " -"importing nodes." -msgstr "配置的类型同样适用于手动导入节点时所指定的核心程序。" - #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm:10 msgid "The content entered is incorrect!" msgstr "输入的内容不正确!" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:706 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:736 msgid "The keep-alive period.(Unit:second)" msgstr "心跳包发送间隔(单位:秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:307 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:6 +msgid "" +"The online upgrade downloads the matching Xray-core linux archive for the " +"current ARCH from the official GitHub release page." +msgstr "" +"在线升级会根据当前架构,从 Xray-core 官方 GitHub 发布页下载匹配的 Linux 压缩" +"包。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:10 +msgid "" +"The online upgrade only tracks the latest stable Mihomo release and " +"downloads the matching linux archive for the current ARCH from the official " +"GitHub release page." +msgstr "" +"在线升级只跟踪 Mihomo 最新稳定版,并根据当前架构从官方 GitHub 发布页下载匹配" +"的 Linux 压缩包。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:14 +msgid "" +"The online upgrade tracks the latest NaiveProxy release and prefers OpenWrt " +"static archives matching the current ARCH before falling back to other " +"release assets." +msgstr "" +"在线升级会跟踪最新 NaiveProxy 发布版,并优先选择与当前架构匹配的 OpenWrt " +"static 压缩包,找不到时再回退到其他发布资产。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:740 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:795 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:89 +msgid "This Clash total node is not active. Apply this node first." +msgstr "该 Clash 总节点当前未启用,请先应用该节点。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel.htm:6 +msgid "" +"This panel is available only when the current Clash total node is active." +msgstr "该面板仅在当前 Clash 总节点已启用时可用。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_table.htm:272 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:126 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:277 +msgid "This section contains no values yet" +msgstr "该部分目前无任何内容。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:485 msgid "Through proxy update" msgstr "通过代理更新" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:309 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:487 msgid "Through proxy update list, Not Recommended" msgstr "通过路由器自身代理更新订阅" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:823 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm:78 +msgid "Timeout" +msgstr "超时" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:853 msgid "Timeout for establishing a connection to server(second)" msgstr "连接超时时间(单位:秒)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:162 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:190 -msgid "Tips: Dnsproxy DNS Parse List Path:" -msgstr "提示:Dnsproxy 的 DNS 解析列表路径:" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:736 -msgid "To Bottom" -msgstr "置底" - -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:733 -msgid "To Top" -msgstr "置顶" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:390 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:260 msgid "To send noise packets, select \"Noise\" in Xray Settings." msgstr "在 Xray 设置中勾选 “噪声” 以发送噪声包。" @@ -2191,53 +2594,57 @@ msgstr "在 Xray 设置中勾选 “噪声” 以发送噪声包。" msgid "Total Records:" msgstr "新的总记录数:" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:920 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:950 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:177 msgid "Transport" msgstr "传输协议" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:620 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:637 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:238 msgid "Transport Protocol" msgstr "传输协议" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:315 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:351 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:444 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:91 +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:399 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:412 msgid "Trojan" -msgstr "" +msgstr "Trojan" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:412 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:362 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:160 +msgid "Trojan Password" +msgstr "Trojan 密码" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/advanced_switch_compact.htm:79 +msgid "Try Count" +msgstr "重试次数" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:282 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:674 msgid "Type" msgstr "类型" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:622 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1662 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:248 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:639 msgid "UDP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:385 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:255 msgid "" "UDP noise, Under some circumstances it can bypass some UDP based protocol " "restrictions." msgstr "UDP 噪声,在某些情况下可以绕过一些针对 UDP 协议的限制。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:530 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:524 msgid "UDP over TCP" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:802 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:832 msgid "UDP relay mode" msgstr "UDP 中继模式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:229 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:258 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:195 msgid "UDP upstream" msgstr "UDP 上游" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:227 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:256 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:193 msgid "UDP/TCP upstream" msgstr "UDP/TCP 上游" @@ -2245,499 +2652,759 @@ msgstr "UDP/TCP 上游" msgid "UL Restore" msgstr "上传恢复" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:322 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:524 msgid "URL Test Address" msgstr "URL 测试地址" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:95 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:153 +msgid "UUID" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:92 msgid "Unable to copy SSR to clipboard." msgstr "无法复制 SSR 网址到剪贴板。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:25 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:35 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:32 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua:42 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:109 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:110 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:111 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:226 msgid "Unknown" msgstr "未知" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:311 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe.htm:16 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:80 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:82 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:130 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:215 +msgid "Unknown error" +msgstr "未知错误" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:36 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:76 +msgid "Unsupported ARCH" +msgstr "不支持当前架构" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:79 +msgid "Unsupported component" +msgstr "不支持的组件" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:265 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:266 +msgid "Update" +msgstr "更新" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:497 msgid "Update All Subscribe Servers" -msgstr "更新所有订阅服务器节点" +msgstr "更新所有订阅节点" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:288 -msgid "Update Subscribe List" -msgstr "更新订阅 URL 列表" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:447 +msgid "Update Interval(min)" +msgstr "更新间隔(分钟)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:121 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:411 +msgid "Update Mode" +msgstr "更新模式" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:418 msgid "Update cycle (Day/Week)" msgstr "更新周期(天/周)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:290 -msgid "Update subscribe url list first" -msgstr "修改订阅 URL 和节点关键字后,请先点击更新" +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:29 +msgid "" +"Update geoip.dat and geosite.dat used by Xray/V2Ray runtime when available." +msgstr "更新 Xray/V2Ray 运行时可用的 geoip.dat 和 geosite.dat 数据库。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1182 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:21 +msgid "" +"Update the Country.mmdb database used by Mihomo/Clash runtime when available." +msgstr "更新 Mihomo/Clash 运行时可用的 Country.mmdb 数据库。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:25 +msgid "" +"Update the GeoSite.dat database used by Mihomo/Clash runtime when available." +msgstr "更新 Mihomo/Clash 运行时可用的 GeoSite.dat 数据库。" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/subscribe_actions_footer.htm:31 +msgid "Updating..." +msgstr "更新中..." + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:157 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:406 +msgid "Upgrade" +msgstr "升级" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:118 +msgid "Upgrade available" +msgstr "发现可升级版本" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:35 +msgid "Upgrade completed" +msgstr "升级完成" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:146 +msgid "Upgrading..." +msgstr "升级中..." + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1212 msgid "Uplink Capacity(Default:Mbps)" msgstr "上行链路容量(默认:Mbps)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1603 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1650 #: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/certupload.htm:3 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_yaml_upload.htm:3 msgid "Upload" msgstr "上传" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:120 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:144 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:544 +msgid "Upload Custom YAML File" +msgstr "上传自定义 YAML 文件" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:546 +msgid "" +"Upload a custom Clash/Mihomo YAML file. The file will be preprocessed and " +"saved as a local Clash node." +msgstr "" +"上传自定义 Clash/Mihomo YAML 文件。文件会先做预处理,再保存为本地 Clash 节" +"点。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:263 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:271 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:276 +msgid "Uploaded YAML validation or preprocessing failed." +msgstr "上传的 YAML 校验或预处理失败。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:128 msgid "Use ChinaDNS-NG query and cache" msgstr "使用 ChinaDNS-NG 查询并缓存" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:168 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:196 -msgid "Use DNS List File" -msgstr "使用 DNS 列表文件" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:264 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:201 msgid "Use DNS from WAN" msgstr "使用 WAN 下发的 DNS" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:265 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:202 msgid "Use DNS from WAN and 114DNS" msgstr "使用 WAN 下发的 DNS 和 114DNS" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:108 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:132 -msgid "Use DNS2SOCKS query and cache" -msgstr "使用 DNS2SOCKS 查询并缓存" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:111 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:135 -msgid "Use DNS2SOCKS-RUST query and cache" -msgstr "使用 DNS2SOCKS-RUST 查询并缓存" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:129 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:122 msgid "Use DNS2TCP query" msgstr "使用 DNS2TCP 查询" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:117 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:141 -msgid "Use DNSPROXY query and cache" -msgstr "使用 DNSPROXY 查询并缓存" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:146 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:131 msgid "Use Local DNS Service listen port 5335" msgstr "使用本机端口为 5335 的 DNS 服务" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:138 -msgid "Use MOSDNS query (Not Support Oversea Mode)" -msgstr "使用 MOSDNS 查询 (不支持海外用户回国模式)" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:114 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:125 msgid "Use MosDNS query" msgstr "使用 MosDNS 查询" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1172 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1202 msgid "" "Use it together with the DNS disguised type. You can fill in any domain." msgstr "配合伪装类型 DNS 使用,可随便填一个域名。" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:107 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:142 +msgid "User Level" +msgstr "用户等级" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:104 msgid "User cancelled." msgstr "用户已取消。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1244 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:332 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1274 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:534 msgid "User-Agent" msgstr "用户代理(User-Agent)" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:295 -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1133 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:313 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm:1028 msgid "Userinfo format error." msgstr "用户信息格式错误。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:489 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:110 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:117 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:481 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:89 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server.lua:122 msgid "Username" msgstr "用户名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:596 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:613 msgid "Users Authentication" msgstr "用户验证" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:395 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:371 msgid "Using incorrect encryption mothod may causes service fail to start" msgstr "输入不正确的参数组合可能会导致服务无法启动" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:342 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:28 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:430 +msgid "V2Ray GEO Databases" +msgstr "V2Ray GEO 数据库" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:337 msgid "V2Ray/XRay" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:441 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:409 msgid "V2Ray/XRay protocol" msgstr "V2Ray/XRay 协议" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:442 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:410 msgid "VLESS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:892 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:922 msgid "VLESS Encryption" msgstr "VLESS 加密" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:443 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:411 msgid "VMess" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1153 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1163 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1183 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1193 msgid "VideoCall (SRTP)" msgstr "视频通话(SRTP)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:752 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:782 msgid "Vmess Protocol" msgstr "VMESS 协议" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:767 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:797 msgid "Vmess UUID" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:884 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:914 msgid "Vmess/VLESS ID (UUID)" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:30 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:26 msgid "WAN Force Proxy IP" msgstr "强制走代理的 WAN IP" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:25 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:21 msgid "WAN IP AC" msgstr "WAN IP 访问控制" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:27 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua:23 msgid "WAN White List IP" msgstr "不走代理的 WAN IP" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:957 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:987 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:252 msgid "WebSocket Host" msgstr "WebSocket 主机名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:963 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:993 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:259 msgid "WebSocket Path" msgstr "WebSocket 路径" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1155 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1165 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1185 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1195 msgid "WechatVideo" msgstr "微信视频通话" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:96 -msgid "When disabled shunt mode, will same time stopped shunt service." -msgstr "当停用分流模式时,将同时停止分流服务。" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:198 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:227 -msgid "When disabled, all AAAA requests are not resolved." -msgstr "当禁用时,不解析所有 AAAA 请求。" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1205 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1235 msgid "When enabled, it occupies IPv6 routing table 1023." msgstr "启用后,将占用 IPv6 路由表 1023。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:187 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:216 -msgid "When two or more DNS servers are deployed, enable this function." -msgstr "当部署两台或两台以上 DNS 服务器时,需要启用该功能。" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:388 +msgid "When selected, it can be accessed from LAN. This may not be safe!" +msgstr "一旦选定,即可通过局域网进行访问。但这样做可能存在安全隐患!" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:161 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:189 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:379 msgid "" -"When use DNS list file, please ensure list file exists and is formatted " -"correctly." -msgstr "当使用 DNS 列表文件时,请确保列表文件存在并且格式正确。" +"When selected, it can only be accessed locally. Recommended when using " +"reverse proxies." +msgstr "一旦启用,它只能在本地进行访问。在使用“反向代理”时建议使用此功能。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:447 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1157 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1167 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:415 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1187 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1197 msgid "WireGuard" msgstr "WireGuard 数据包" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1237 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1267 msgid "Wireguard allows only traffic from specific source IP." msgstr "Wireguard 仅允许特定源 IP 的流量。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1023 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1053 msgid "XHTTP Extra" msgstr "XHTTP 附加项" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1011 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1041 msgid "XHTTP Host" msgstr "XHTTP 主机名" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1002 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1032 msgid "XHTTP Mode" msgstr "XHTTP 模式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1017 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1047 msgid "XHTTP Path" msgstr "XHTTP 路径" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:287 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:87 -msgid "Xray (Hysteria2)" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:207 +msgid "XTLS" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:104 -msgid "Xray (ShadowSocks)" -msgstr "" +#: applications/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua:394 +msgid "Xray" +msgstr "Xray" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:318 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/servers.lua:94 -msgid "Xray (Trojan)" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:355 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:225 msgid "Xray Fragment Settings" msgstr "Xray 分片设置" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:388 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:258 msgid "Xray Noise Packets" msgstr "Xray 噪声数据包" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:252 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:5 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:394 +msgid "Xray-core" +msgstr "Xray-core" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:275 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:20 +msgid "YAML Reload Failed!" +msgstr "YAML 重载失败!" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_main_panel.htm:272 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/clash_panel_button.htm:17 +msgid "YAML Reloaded!" +msgstr "YAML 已重载!" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:137 msgid "adblock_url" msgstr "广告屏蔽更新 URL" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1141 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1171 msgid "aes-128-gcm" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1537 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1584 msgid "allow" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1531 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1578 msgid "allow: Allows use Mux connection." msgstr "allow:允许走 Mux 连接。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1451 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1498 msgid "allowInsecure" msgstr "允许不安全连接" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1236 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1266 msgid "allowedIPs(optional)" msgstr "allowedIPs(可选)" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1352 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1388 msgid "android" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:254 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:139 msgid "anti-AD" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1142 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1172 msgid "chacha20-poly1305" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:92 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:116 msgid "china-operator-ip" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1247 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1348 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1277 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1384 msgid "chrome" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:180 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:208 -msgid "cloudflare-dns.com DNSCrypt SDNS" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1568 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1584 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1615 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1631 msgid "comment_tcpcongestion_disable" msgstr "系统默认值" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1501 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1548 msgid "concurrency" msgstr "TCP 最大并发连接数" -#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm:678 -msgid "connect" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1252 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1282 msgid "curl" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1565 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1581 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1612 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1628 msgid "custom_tcpcongestion" msgstr "连接服务器节点的 TCP 拥塞控制算法" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1358 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1509 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1522 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1394 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1556 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1569 msgid "disable" msgstr "禁用" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:176 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:204 -msgid "dns.sb DNSCrypt SDNS" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:228 +msgid "e.g.: /etc/ssl/fullchain.pem" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1250 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1353 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:233 +msgid "e.g.: /etc/ssl/private.key" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1280 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1389 msgid "edge" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:192 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:221 -msgid "fastest_addr" -msgstr "最快响应" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:239 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:124 msgid "felixonmars/dnsmasq-china-list" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1248 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1349 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1278 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1385 msgid "firefox" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1120 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1150 msgid "gRPC Idle Timeout" msgstr "gPRC 空闲超时" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1093 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1123 msgid "gRPC Mode" msgstr "gRPC 模式" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1087 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1117 msgid "gRPC Service Name" msgstr "gRPC 服务名称" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:82 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:106 msgid "gfwlist Update url" msgstr "GFW 列表更新 URL" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:86 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:110 msgid "gfwlist/gfwlist" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1251 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1281 msgid "golang" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1351 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:552 +msgid "gost-plugin" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1387 msgid "ios" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:190 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:219 -msgid "load_balance" -msgstr "负载均衡" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:562 +msgid "kcptun" +msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:805 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:835 msgid "lossless UDP relay using QUIC streams" msgstr "使用 QUIC 流的无损 UDP 中继" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:804 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:300 +msgid "mKCP Camouflage Type" +msgstr "mKCP 伪装(混淆)类型" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:46 +msgid "mihomo binary not found in archive" +msgstr "压缩包中未找到 mihomo 二进制文件" + +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:47 +msgid "naive binary not found in archive" +msgstr "压缩包中未找到 naive 二进制文件" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:834 msgid "native UDP characteristics" msgstr "原生 UDP 特性" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:99 -msgid "nfip_url" -msgstr "Netflix IP 段更新 URL" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:521 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1337 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:514 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1373 msgid "none" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:551 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:546 msgid "obfs-local" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:191 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:220 -msgid "parallel" -msgstr "并行查询" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1355 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1391 msgid "qq" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1356 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1392 msgid "random" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1357 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1393 msgid "randomized" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1536 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1583 msgid "reject" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1249 -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1350 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:561 +msgid "restls" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1279 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1386 msgid "safari" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:739 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:769 msgid "shadow-TLS SNI" msgstr "服务器名称指示" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:560 -msgid "shadow-tls" -msgstr "" - -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:717 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:747 msgid "shadowTLS protocol Version" msgstr "ShadowTLS 协议版本" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1538 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1585 msgid "skip" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1532 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1579 msgid "" "skip: Not use Mux module to carry UDP 443 traffic, Use original UDP " "transmission method of proxy protocol." msgstr "" "skip:不使用 Mux 模块承载 UDP 443 流量,将使用代理协议原本的 UDP 传输方式。" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1328 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1364 msgid "spiderX" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:83 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:107 msgid "v2fly/domain-list-community" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:554 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:549 msgid "v2ray-plugin" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:289 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:223 msgid "valid address:port" msgstr "有效的地址:端口" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:101 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/server-config.lua:80 msgid "warning! Please do not reuse the port!" msgstr "警告!请不要重复使用端口!" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:557 +#: applications/luci-app-ssr-plus/luasrc/view/shadowsocksr/component.htm:45 +msgid "xray binary not found in archive" +msgstr "压缩包中未找到 xray 二进制文件" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:555 msgid "xray-plugin" msgstr "" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1514 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1561 msgid "xudpConcurrency" msgstr "UDP 最大并发连接数" -#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1527 +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1574 msgid "xudpProxyUDP443" msgstr "对被代理的 UDP/443 流量处理方式" +#~ msgid "Shadowsocks password" +#~ msgstr "shadowsocks密码" + +#~ msgid "Are you sure you want to delete subscribe link: %s ? " +#~ msgstr "确定删除此订阅链接:%s 吗?" + +#~ msgid "%s Node Use Type" +#~ msgstr "%s 节点使用类型" + +#~ msgid "Drag reorder applies within the current page only." +#~ msgstr "拖拽排序仅在当前分页内生效。" + +#~ msgid "Anti-pollution DNS Server For Shunt Mode" +#~ msgstr "分流模式下的访问国外域名 DNS 服务器" + +#~ msgid "Are you sure to delete this node?" +#~ msgstr "是否真的要删除该节点?" + +#~ msgid "Auto" +#~ msgstr "自动" + +#~ msgid "ChinaDNS-NG shunt query protocol" +#~ msgstr "ChinaDNS-NG 分流查询协议" + +#~ msgid "Click here to view or manage the DNS list file" +#~ msgstr "点击此处查看或管理 DNS 列表文件" + +#~ msgid "" +#~ "Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-" +#~ "query and other format)." +#~ msgstr "" +#~ "自定义 DNS 服务器(支持格式:IP:端口、tls://IP:端口、https://IP/dns-query " +#~ "及其他格式)。" + +#~ msgid "Customize Netflix IP Url" +#~ msgstr "" +#~ "自定义 Netflix IP 段更新 URL(默认项目地址:https://github.com/QiuSimons/" +#~ "Netflix_IP)" + +#~ msgid "DNS Query Mode For Shunt Mode" +#~ msgstr "分流模式下的 DNS 查询模式" + +#~ msgid "Defines the upstreams logic mode" +#~ msgstr "定义上游逻辑模式" + +#~ msgid "" +#~ "Defines the upstreams logic mode, possible values: load_balance, " +#~ "parallel, fastest_addr (default: load_balance)." +#~ msgstr "" +#~ "定义上游逻辑模式,可选择值:负载均衡、并行查询、最快响应(默认值:负载均" +#~ "衡)。" + +#~ msgid "Disable IPv6 In MosDNS Query Mode (Shunt Mode)" +#~ msgstr "禁止 MosDNS 返回 IPv6 记录 (分流模式)" + +#~ msgid "Disable IPv6 in MOSDNS query mode" +#~ msgstr "禁止 MOSDNS 返回 IPv6 记录" + +#~ msgid "Disable IPv6 query mode" +#~ msgstr "禁止返回 IPv6 记录" + +#~ msgid "Drag to reorder" +#~ msgstr "拖动以重排" + +#~ msgid "Enable Netflix Mode" +#~ msgstr "启用 Netflix 分流模式" + +#~ msgid "External Proxy Mode" +#~ msgstr "分流服务器(前置)代理" + +#~ msgid "Forward Netflix Proxy through Main Proxy" +#~ msgstr "分流服务器流量通过主服务节点中转代理转发" + +#~ msgid "Game Mode UDP Server" +#~ msgstr "游戏模式 UDP 中继服务器" + +#~ msgid "Netflix Domain List" +#~ msgstr "Netflix 分流域名列表" + +#~ msgid "Netflix IP Data" +#~ msgstr "【Netflix IP 段】数据库" + +#~ msgid "Netflix Node" +#~ msgstr "Netflix 分流服务器" + +#~ msgid "Network Tunnel" +#~ msgstr "网络隧道" + +#~ msgid "Network interface to use" +#~ msgstr "使用的网络接口" + +#~ msgid "No available core (Trojan or Xray) to import this node." +#~ msgstr "没有可用的核心(Trojan 或 Xray)可以导入此节点。" + +#~ msgid "Oversea Mode" +#~ msgstr "海外用户回国模式" + +#~ msgid "Redirect traffic to this network interface" +#~ msgstr "分流到这个网络接口" + +#~ msgid "Client rules exported to CSV text." +#~ msgstr "客户端规则已导出为 CSV 文本。" + +#~ msgid "Client rules imported from CSV text. Click Save to apply." +#~ msgstr "客户端规则已从 CSV 文本导入,请点击保存生效。" + +#~ msgid "Same as Global Server" +#~ msgstr "与全局服务器相同" + +#~ msgid "Save Order" +#~ msgstr "保存当前顺序" + +#~ msgid "Save failed!" +#~ msgstr "保存失败!" + +#~ msgid "Saved current page order successfully." +#~ msgstr "保存当前页面顺序成功。" + +#~ msgid "Saving the new order..." +#~ msgstr "正在保存新的顺序…" + +#~ msgid "Select DNS parse Mode" +#~ msgstr "选择 DNS 解析方式" + +#~ msgid "Server" +#~ msgstr "服务器" + +#~ msgid "Set Single DNS" +#~ msgstr "设置单个 DNS" + +#~ msgid "Supports only Xray node types." +#~ msgstr "仅支持 Xray 类型节点。" + +#~ msgid "TLS handshake test, latency for reference only" +#~ msgstr "TLS握手测试,延时仅供参考" + +#~ msgid "Test" +#~ msgstr "测试" + +#~ msgid "Testing..." +#~ msgstr "检测中…" + +#~ msgid "" +#~ "The configured type also applies to the core specified when manually " +#~ "importing nodes." +#~ msgstr "配置的类型同样适用于手动导入节点时所指定的核心程序。" + +#~ msgid "Tips: Dnsproxy DNS Parse List Path:" +#~ msgstr "提示:Dnsproxy 的 DNS 解析列表路径:" + +#~ msgid "To Bottom" +#~ msgstr "置底" + +#~ msgid "To Top" +#~ msgstr "置顶" + +#~ msgid "Use DNS List File" +#~ msgstr "使用 DNS 列表文件" + +#~ msgid "Use DNS2SOCKS query and cache" +#~ msgstr "使用 DNS2SOCKS 查询并缓存" + +#~ msgid "Use DNS2SOCKS-RUST query and cache" +#~ msgstr "使用 DNS2SOCKS-RUST 查询并缓存" + +#~ msgid "Use MOSDNS query (Not Support Oversea Mode)" +#~ msgstr "使用 MOSDNS 查询 (不支持海外用户回国模式)" + +#~ msgid "When disabled shunt mode, will same time stopped shunt service." +#~ msgstr "当停用分流模式时,将同时停止分流服务。" + +#~ msgid "When disabled, all AAAA requests are not resolved." +#~ msgstr "当禁用时,不解析所有 AAAA 请求。" + +#~ msgid "When two or more DNS servers are deployed, enable this function." +#~ msgstr "当部署两台或两台以上 DNS 服务器时,需要启用该功能。" + +#~ msgid "" +#~ "When use DNS list file, please ensure list file exists and is formatted " +#~ "correctly." +#~ msgstr "当使用 DNS 列表文件时,请确保列表文件存在并且格式正确。" + +#~ msgid "fastest_addr" +#~ msgstr "最快响应" + +#~ msgid "load_balance" +#~ msgstr "负载均衡" + +#~ msgid "nfip_url" +#~ msgstr "Netflix IP 段更新 URL" + +#~ msgid "parallel" +#~ msgstr "并行查询" + #~ msgid "Congestion" #~ msgstr "拥塞控制" @@ -2927,9 +3594,6 @@ msgstr "对被代理的 UDP/443 流量处理方式" #~ msgid "DNS Server IP:Port" #~ msgstr "DNS 服务器 IP:Port" -#~ msgid "Update" -#~ msgstr "更新" - #~ msgid "Router Self AC" #~ msgstr "路由器自身代理设置" @@ -2968,3 +3632,36 @@ msgstr "对被代理的 UDP/443 流量处理方式" #~ msgid "Reset Error" #~ msgstr "重置错误" + +#~ msgid "Selector" +#~ msgstr "选择器" + +#~ msgid "DIRECT" +#~ msgstr "直连" + +#~ msgid "REJECT" +#~ msgstr "拒绝" + +#~ msgid "Proxy" +#~ msgstr "代理" + +#~ msgid "Domestic" +#~ msgstr "国内" + +#~ msgid "GlobalTV" +#~ msgstr "国际流媒体" + +#~ msgid "AsianTV" +#~ msgstr "亚洲流媒体" + +#~ msgid "Others" +#~ msgstr "其他" + +#~ msgid "GLOBAL" +#~ msgstr "全局" + +#~ msgid "Subscribe complete, refreshing page..." +#~ msgstr "订阅完成,正在刷新页面..." + +#~ msgid "Include Mihomo (Clash Support)" +#~ msgstr "包含 Mihomo(Clash 支持)" diff --git a/luci-app-ssr-plus/root/etc/hotplug.d/iface/99-ssrplus-pppoe b/luci-app-ssr-plus/root/etc/hotplug.d/iface/99-ssrplus-pppoe new file mode 100644 index 000000000..fc969f711 --- /dev/null +++ b/luci-app-ssr-plus/root/etc/hotplug.d/iface/99-ssrplus-pppoe @@ -0,0 +1,22 @@ +#!/bin/sh + +[ "$ACTION" = "ifup" ] || exit 0 +[ "$INTERFACE" = "wan" ] || exit 0 + +WAN_PROTO="$(uci -q get network.wan.proto 2>/dev/null)" +[ "$WAN_PROTO" = "pppoe" ] || exit 0 + +LOCK_FILE="/var/run/ssrplus-pppoe-hotplug.lock" + +( + if ! mkdir "$LOCK_FILE" 2>/dev/null; then + exit 0 + fi + + trap 'rmdir "$LOCK_FILE" 2>/dev/null' EXIT INT TERM + + sleep 10 + /etc/init.d/shadowsocksr restart >/dev/null 2>&1 +) >/dev/null 2>&1 & + +exit 0 diff --git a/luci-app-ssr-plus/root/etc/init.d/shadowsocksr b/luci-app-ssr-plus/root/etc/init.d/shadowsocksr index 1d5c32973..1970fa948 100755 --- a/luci-app-ssr-plus/root/etc/init.d/shadowsocksr +++ b/luci-app-ssr-plus/root/etc/init.d/shadowsocksr @@ -21,6 +21,10 @@ PERSIST_DIR="/usr/share/nftables.d/ruleset-post" PERSIST_FILE="$PERSIST_DIR/99-shadowsocksr.nft" BACKUP_DIR="/etc/ssrplus/ssrplus-persist" BACKUP_FILE="$BACKUP_DIR/99-shadowsocksr.save" +CLASH_CONFIG_DIR="/etc/ssrplus/clash" +CLASH_API_PORT="16756" +CLASH_OUTBOUND_MARK="255" +CLASH_YAML_HELPER="/usr/share/shadowsocksr/clash_yaml.lua" # 设置 DNSMASQ_CONF_DIR 和 TMP_DNSMASQ_PATH if [ -f /etc/openwrt_release ]; then # 获取默认的 DNSMASQ 配置 ID @@ -42,8 +46,10 @@ shunt_config_file= local_config_file= shunt_dns_config_file= tmp_local_port= +DNS2TCP_FILTER_AAAA_SUPPORTED= ARG_UDP= +ARG_UDP_RULES= dns_port="5335" #dns port china_dns_port="5333" #china_dns_port @@ -59,6 +65,7 @@ server_count=0 redir_tcp=0 redir_udp=0 local_enable=0 +http_enable=0 kcp_enable_flag=0 pdnsd_enable_flag=0 @@ -66,17 +73,27 @@ USE_TABLES="" HAS_NFT=0 HAS_IPSET=0 HAS_IPT=0 +HAS_FW3=0 HAS_FW4=0 DNSMASQ_IPSET=0 DNSMASQ_NFTSET=0 switch_server=$1 CRON_FILE=/etc/crontabs/root -EXTRA_COMMANDS='reset' -EXTRA_HELP=" reset Reset to default settings" +EXTRA_COMMANDS='reset clash_cache' +EXTRA_HELP=" reset Reset to default settings + clash_cache Download and cache Clash node config" #extra_command "reset" "Reset to default settings" PS="/bin/busybox ps" +ps_list() { + if $PS -w >/dev/null 2>&1; then + $PS -w + else + $PS + fi +} + uci_get_by_name() { local ret=$(uci get $NAME.$1.$2 2>/dev/null) echo ${ret:=$3} @@ -98,21 +115,478 @@ uci_set_by_type() { } uci_get_by_cfgid() { - local ret=$(uci show $NAME.@$1[0].$2 | awk -F '.' '{print $2}' 2>/dev/null) + local key="$NAME.@$1[0]" + [ -n "$2" ] && key="$key.$2" + local ret=$(uci show "$key" 2>/dev/null | awk -F '[.=]' 'NR==1 {print $2}') echo ${ret:=$3} } +get_filter_aaaa() { + local ret="$(uci get $NAME.@global[0].filter_aaaa 2>/dev/null)" + [ -z "$ret" ] && ret="$(uci get $NAME.@global[0].mosdns_ipv6 2>/dev/null)" + echo "${ret:=1}" +} + +supports_dns2tcp_filter_aaaa() { + local dns2tcp_bin="${1:-$(first_type dns2tcp)}" + + [ -n "$dns2tcp_bin" ] || return 1 + + if [ -n "$DNS2TCP_FILTER_AAAA_SUPPORTED" ]; then + [ "$DNS2TCP_FILTER_AAAA_SUPPORTED" = "1" ] + return $? + fi + + if "$dns2tcp_bin" -h 2>&1 | grep -Eq '(^|[[:space:]])-A([[:space:]]|$)'; then + DNS2TCP_FILTER_AAAA_SUPPORTED=1 + return 0 + fi + + DNS2TCP_FILTER_AAAA_SUPPORTED=0 + return 1 +} + +start_dns2tcp() { + local dnsserver="$1" + local dns2tcp_bin="$(first_type dns2tcp)" + + if [ "$(get_filter_aaaa)" = "1" ]; then + if supports_dns2tcp_filter_aaaa "$dns2tcp_bin"; then + ln_start_bin "$dns2tcp_bin" dns2tcp -L "127.0.0.1#$dns_port" -R "${dnsserver/:/#}" -A + else + echolog "警告:当前 dns2tcp 不支持 AAAA 过滤参数(-A),已改为不带该参数启动。请升级 dns2tcp 版本。" + ln_start_bin "$dns2tcp_bin" dns2tcp -L "127.0.0.1#$dns_port" -R "${dnsserver/:/#}" + fi + else + ln_start_bin "$dns2tcp_bin" dns2tcp -L "127.0.0.1#$dns_port" -R "${dnsserver/:/#}" + fi +} + +supports_builtin_dns() { + case "$1" in + clash|tuic|v2ray|ss) + return 0 + ;; + *) + return 1 + ;; + esac +} + +get_builtin_dns_fallback_mode() { + if is_finded "dns2tcp"; then + echo "1" + elif is_finded "chinadns-ng"; then + echo "6" + elif is_finded "mosdns"; then + echo "4" + else + echo "0" + fi +} + +get_dns2tcp_fallback_mode() { + if is_finded "dns2tcp"; then + echo "1" + else + echo "0" + fi +} + +force_dns2tcp_fallback() { + local reason="$1" + local fallback_mode="$(get_dns2tcp_fallback_mode)" + + if [ "$fallback_mode" = "1" ]; then + echolog "提示:${reason},强制回退到 dns2tcp 模式。" + echo "1" + return 0 + fi + + echolog "错误:${reason},且未安装 dns2tcp,启动终止。" + return 1 +} + +is_builtin_dns_active() { + local global_server_type="$(uci_get_by_name "$GLOBAL_SERVER" type)" + case "$global_server_type" in + clash|tuic|ss) + ps_list | grep -v "grep" | grep -q "ssr-retcp" + ;; + v2ray) + ps_list | grep -v "grep" | grep -q "$TMP_PATH/.*ssr-retcp\\.json" + ;; + *) + return 1 + ;; + esac +} + +get_default_node_local_port() { + uci_get_by_type global default_node_local_port 1234 +} + +get_configured_threads() { + local threads + if [ "$(uci_get_by_type global threads 0)" = "0" ]; then + threads=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null) + else + threads=$(uci_get_by_type global threads 1) + fi + + case "$threads" in + ''|*[!0-9]*) + threads=1 + ;; + esac + + [ "$threads" -lt 1 ] && threads=1 + echo "$threads" +} + +get_clash_api_secret() { + echo "${1}_ssrplus_clash" +} + +get_clash_workdir() { + echo "$TMP_PATH/clash-$1" +} + +get_clash_cache_file() { + echo "$CLASH_CONFIG_DIR/$1.yaml" +} + +get_clash_state_file() { + echo "$CLASH_CONFIG_DIR/$1.cache.db" +} + +get_tuic_workdir() { + echo "$TMP_PATH/tuic-$1" +} + +get_tuic_runtime_file() { + echo "$(get_tuic_workdir "$1")/config.yaml" +} + +get_ss_mihomo_workdir() { + echo "$TMP_PATH/ss-$1" +} + +get_ss_mihomo_runtime_file() { + echo "$(get_ss_mihomo_workdir "$1")/config.yaml" +} + +get_ss_server_mihomo_workdir() { + echo "$TMP_PATH/ss-server-$1" +} + +get_ss_server_mihomo_runtime_file() { + echo "$(get_ss_server_mihomo_workdir "$1")/config.yaml" +} + +link_mihomo_geodata() { + local workdir="$1" + + [ -n "$workdir" ] || return 0 + + [ -s /usr/share/shadowsocksr/Country.mmdb ] && ln -sf /usr/share/shadowsocksr/Country.mmdb "$workdir/Country.mmdb" + [ -s /usr/share/shadowsocksr/Country.mmdb ] && ln -sf /usr/share/shadowsocksr/Country.mmdb "$workdir/geoip.metadb" + [ ! -e "$workdir/Country.mmdb" ] && [ -s /etc/openclash/Country.mmdb ] && ln -sf /etc/openclash/Country.mmdb "$workdir/Country.mmdb" + [ ! -e "$workdir/geoip.metadb" ] && [ -s /etc/openclash/Country.mmdb ] && ln -sf /etc/openclash/Country.mmdb "$workdir/geoip.metadb" + + if [ -s /usr/share/v2ray/geosite.dat ]; then + ln -sf /usr/share/v2ray/geosite.dat "$workdir/GeoSite.dat" + ln -sf /usr/share/v2ray/geosite.dat "$workdir/geosite.dat" + else + [ -s /etc/openclash/GeoSite.dat ] && ln -sf /etc/openclash/GeoSite.dat "$workdir/GeoSite.dat" + [ -s /etc/openclash/geosite.dat ] && ln -sf /etc/openclash/geosite.dat "$workdir/geosite.dat" + fi + + if [ -s /usr/share/v2ray/geoip.dat ]; then + ln -sf /usr/share/v2ray/geoip.dat "$workdir/geoip.dat" + else + [ -s /etc/openclash/geoip.dat ] && ln -sf /etc/openclash/geoip.dat "$workdir/geoip.dat" + fi +} + +prepare_tuic_runtime_config() { + local sid="$1" + local local_port="$2" + local socks_port="$3" + local instance_key="${4:-$sid}" + local run_mode="${5:-redir}" + local runtime_file="$(get_tuic_runtime_file "$instance_key")" + local workdir="$(get_tuic_workdir "$instance_key")" + + [ -n "$sid" ] || return 1 + [ -n "$local_port" ] || return 1 + + mkdir -p "$workdir" + if ! /usr/bin/lua "$CLASH_YAML_HELPER" tuic "$sid" "$runtime_file" "$local_port" "$socks_port" "$run_mode" >/dev/null 2>&1; then + echolog "TUIC 节点运行配置生成失败:$sid" + return 1 + fi + + # Reuse existing local geodata files so Mihomo does not have to fetch them. + link_mihomo_geodata "$workdir" +} + +prepare_ss_mihomo_runtime_config() { + local sid="$1" + local local_port="$2" + local socks_port="$3" + local instance_key="${4:-$sid}" + local run_mode="${5:-redir}" + local runtime_file="$(get_ss_mihomo_runtime_file "$instance_key")" + local workdir="$(get_ss_mihomo_workdir "$instance_key")" + + [ -n "$sid" ] || return 1 + [ -n "$local_port" ] || return 1 + + mkdir -p "$workdir" + if ! /usr/bin/lua "$CLASH_YAML_HELPER" ss "$sid" "$runtime_file" "$local_port" "$socks_port" "$run_mode" >/dev/null 2>&1; then + echolog "Shadowsocks 单节点 Mihomo 运行配置生成失败:$sid" + return 1 + fi + + link_mihomo_geodata "$workdir" +} + +prepare_ss_server_mihomo_runtime_config() { + local sid="$1" + local runtime_file="$(get_ss_server_mihomo_runtime_file "$sid")" + local workdir="$(get_ss_server_mihomo_workdir "$sid")" + + [ -n "$sid" ] || return 1 + + mkdir -p "$workdir" + if ! /usr/bin/lua "$CLASH_YAML_HELPER" ss_server "$sid" "$runtime_file" >/dev/null 2>&1; then + echolog "Shadowsocks 服务端 Mihomo 配置生成失败:$sid" + return 1 + fi +} + +download_clash_config() { + local sid="$1" + local clash_url="$(uci_get_by_name "$sid" clash_url)" + local clash_path="$(uci_get_by_name "$sid" clash_path)" + local clash_user_agent="$(uci_get_by_name "$sid" clash_user_agent clash)" + local cache_file="$(get_clash_cache_file "$sid")" + local tmp_file="$TMP_PATH/clash-$sid.download.yaml" + + if [ -n "$clash_path" ] && [ -s "$clash_path" ]; then + mkdir -p "$CLASH_CONFIG_DIR" + cp -f "$clash_path" "$cache_file" + echolog "Clash 本地配置加载成功:$clash_path" + return 0 + fi + + [ -n "$clash_url" ] || { + echolog "Clash 节点未配置订阅 URL/本地配置,无法启动。" + return 1 + } + + mkdir -p "$CLASH_CONFIG_DIR" + if curl -fsSL --connect-timeout 15 --retry 2 --insecure -A "$clash_user_agent" "$clash_url" -o "$tmp_file"; then + if /usr/bin/lua "$CLASH_YAML_HELPER" validate "$tmp_file" >/dev/null 2>&1; then + mv -f "$tmp_file" "$cache_file" + echolog "Clash 配置下载成功:$clash_url" + return 0 + else + echolog "Clash 配置校验失败:$clash_url" + rm -f "$tmp_file" + fi + else + echolog "Clash 配置下载失败:$clash_url" + fi + + if [ -s "$cache_file" ]; then + echolog "使用缓存的 Clash 配置继续启动。" + return 0 + fi + + return 1 +} + +clash_cache() { + local sid="$2" + [ -n "$sid" ] || sid="$1" + [ -n "$sid" ] || return 1 + [ "$(uci_get_by_name "$sid" type)" = "clash" ] || return 1 + mkdir -p "$TMP_PATH" "$CLASH_CONFIG_DIR" + download_clash_config "$sid" +} + +filter_clash_yaml_proxies() { + local yaml_file="$1" + local filter_words="$(uci_get_by_type server_subscribe filter_words '过期时间/剩余流量')" + local names_file="$TMP_PATH/clash-proxy-names.$$" + local removed_file="$TMP_PATH/clash-proxy-removed.$$" + local removed_count=0 + + [ -n "$yaml_file" ] && [ -s "$yaml_file" ] || return 0 + [ -n "$filter_words" ] || return 0 + + if ! /usr/bin/lua "$CLASH_YAML_HELPER" validate "$yaml_file" >/dev/null 2>&1; then + return 0 + fi + + removed_count="$(/usr/bin/lua "$CLASH_YAML_HELPER" filter "$yaml_file" "$filter_words" 2>/dev/null || echo 0)" + if [ -n "$removed_count" ] && [ "$removed_count" -gt 0 ] 2>/dev/null; then + echolog "Clash YAML 共过滤节点数量: $removed_count" + fi + + rm -f "$names_file" "$removed_file" +} + +prepare_clash_runtime_config() { + local sid="$1" + local local_port="$2" + local socks_port="$3" + local cache_file="$(get_clash_cache_file "$sid")" + local workdir="$(get_clash_workdir "$sid")" + local raw_file="$workdir/raw.yaml" + local overlay_file="$workdir/overlay.yaml" + local runtime_file="$workdir/config.yaml" + local state_file="$(get_clash_state_file "$sid")" + local merge_stats="" + local filled_groups=0 + local stripped_script_rules=0 + local clash_secret="$(get_clash_api_secret "$sid")" + local dns_ipv4_only="$(get_filter_aaaa)" + local dns_mode="$(uci_get_by_type global pdnsd_enable 0)" + + [ -s "$cache_file" ] || return 1 + + mkdir -p "$workdir" "$CLASH_CONFIG_DIR" + cp -f "$cache_file" "$raw_file" + filter_clash_yaml_proxies "$raw_file" + + cat >"$overlay_file" <<-EOF + redir-port: $local_port + tproxy-port: $local_port + external-controller: 127.0.0.1:$CLASH_API_PORT + secret: $clash_secret + allow-lan: true + bind-address: 0.0.0.0 + routing-mark: $CLASH_OUTBOUND_MARK + profile: + store-selected: true + tun: + enable: false + dns: + enable: $( [ "$dns_mode" = "7" ] && echo "true" || echo "false" ) + enhanced-mode: redir-host + listen: 127.0.0.1:$dns_port + ipv6: $( [ "$dns_ipv4_only" = "1" ] && echo "false" || echo "true" ) + EOF + + if [ -n "$socks_port" ] && [ "$socks_port" != "0" ]; then + echo "socks-port: $socks_port" >>"$overlay_file" + fi + + merge_stats="$(/usr/bin/lua "$CLASH_YAML_HELPER" merge "$raw_file" "$overlay_file" "$runtime_file" 2>/dev/null)" || return 1 + filled_groups="$(printf '%s\n' "$merge_stats" | sed -n 's/.*filled_groups=\([0-9][0-9]*\).*/\1/p' | tail -n1)" + stripped_script_rules="$(printf '%s\n' "$merge_stats" | sed -n 's/.*stripped_script_rules=\([0-9][0-9]*\).*/\1/p' | tail -n1)" + [ -n "$filled_groups" ] || filled_groups=0 + [ -n "$stripped_script_rules" ] || stripped_script_rules=0 + if [ "$filled_groups" -gt 0 ] 2>/dev/null; then + echolog "Clash YAML 兼容修复:共为 $filled_groups 个空代理组回填 DIRECT。" + fi + if [ "$stripped_script_rules" -gt 0 ] 2>/dev/null; then + echolog "Clash YAML 兼容修复:共移除 $stripped_script_rules 条不兼容的 SCRIPT 规则。" + fi + local client_policy_stats="" + local client_rules=0 + client_policy_stats="$(/usr/bin/lua "$CLASH_YAML_HELPER" append_client_policy_rules "$runtime_file" "$sid" 2>/dev/null)" || true + client_rules="$(printf '%s\n' "$client_policy_stats" | sed -n 's/.*client_rules=\([0-9][0-9]*\).*/\1/p' | tail -n1)" + [ -n "$client_rules" ] || client_rules=0 + if [ "$client_rules" -gt 0 ] 2>/dev/null; then + echolog "Clash YAML 自定义客户端代理规则已注入:$client_rules 条。" + fi + + # Persist Mihomo's store-selected cache across service restarts. + if [ -f "$workdir/cache.db" ] && [ ! -L "$workdir/cache.db" ]; then + if [ ! -e "$state_file" ]; then + mv -f "$workdir/cache.db" "$state_file" + else + rm -f "$workdir/cache.db" + fi + fi + rm -f "$workdir/cache.db" + ln -sf "$state_file" "$workdir/cache.db" + + # Reuse existing local geodata files so Mihomo does not have to fetch them + # during startup in environments where DNS or outbound access is not ready yet. + link_mihomo_geodata "$workdir" +} + +resolve_global_clash_socks_port() { + local socks_enabled=$(uci_get_by_type socks5_proxy enabled 0) + local socks_server=$(uci_get_by_type socks5_proxy server nil) + local socks_port=$(uci_get_by_type socks5_proxy local_port 1080) + local http_enabled=$(uci_get_by_type http_proxy enabled 0) + local http_server=$(uci_get_by_type http_proxy server nil) + local http_port="$socks_port" + local resolved_port="" + + [ "$socks_server" = "same" ] && socks_server="$GLOBAL_SERVER" + [ "$http_server" = "same" ] && http_server="$GLOBAL_SERVER" + + if [ "$socks_enabled" = "1" ] && [ "$socks_server" = "$GLOBAL_SERVER" ]; then + resolved_port="$socks_port" + fi + + if [ "$http_enabled" = "1" ] && [ "$http_server" = "$GLOBAL_SERVER" ]; then + if [ -n "$resolved_port" ] && [ "$resolved_port" != "$http_port" ]; then + echolog "Global_HTTP: Mihomo/Clash 仅支持单个 socks-port,HTTP 代理将复用全局 SOCKS5 端口 $resolved_port。" + else + resolved_port="$http_port" + fi + fi + + echo "$resolved_port" +} + +supports_builtin_socks() { + case "$1" in + ss|clash|tuic|v2ray) + return 0 + ;; + *) + return 1 + ;; + esac +} + +supports_builtin_http() { + case "$1" in + clash) + return 0 + ;; + *) + return 1 + ;; + esac +} + get_host_ip() { local host=$(uci_get_by_name $1 server) - local ip=$host - if [ -z "$(echo $host | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}")" ]; then - if [ "$host" == "${host#*:[0-9a-fA-F]}" ]; then - ip=$(resolveip -4 -t 3 $host | awk 'NR==1{print}') - [ -z "$ip" ] && ip=$(curl -sSL "http://119.29.29.29/d?dn=$host" | awk -F ';' '{print $1}') - fi + [ -n "$host" ] || { + echo "" + return + } + host="${host#\[}" + host="${host%\]}" + local ip="" + if echo "$host" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then + ip="$host" + elif ! echo "$host" | grep -q ':'; then + ip=$(resolveip -4 -t 3 "$host" | awk 'NR==1{print}') + [ -z "$ip" ] && ip=$(curl -sSL "http://119.29.29.29/d?dn=$host" | awk -F ';' '{print $1}') fi [ -z "$ip" ] || uci_set_by_name $1 ip $ip - [ -n "$ip" ] || ip="$(uci_get_by_name $1 ip "ERROR")" + [ -n "$ip" ] || ip="" local chinadns="$(uci_get_by_type global chinadns_forward)" if [ -n "$chinadns" ] && [ "$ip" != "$host" ]; then @@ -138,7 +612,13 @@ echolog() { add_cron() { touch $CRON_FILE sed -i '/ssrplus.log/d' $CRON_FILE - [ $(uci_get_by_type server_subscribe auto_update 0) -eq 1 ] && echo "$(uci_get_by_type server_subscribe auto_update_min_time) $(uci_get_by_type server_subscribe auto_update_day_time) * * $(uci_get_by_type server_subscribe auto_update_week_time) /usr/share/shadowsocksr/ssrplusupdate.sh >$LOG_FILE" >>$CRON_FILE + if [ "$(uci_get_by_type server_subscribe auto_update 0)" -eq 1 ]; then + if [ "$(uci_get_by_type server_subscribe config_auto_update_mode 0)" = "1" ]; then + echo "* * * * * /usr/share/shadowsocksr/ssrplusupdate.sh loop >>$LOG_FILE 2>&1" >>$CRON_FILE + else + echo "$(uci_get_by_type server_subscribe auto_update_min_time) $(uci_get_by_type server_subscribe auto_update_day_time) * * $(uci_get_by_type server_subscribe auto_update_week_time) /usr/share/shadowsocksr/ssrplusupdate.sh >$LOG_FILE" >>$CRON_FILE + fi + fi crontab $CRON_FILE } @@ -179,7 +659,42 @@ _exit() { } first_type() { - type -t -p "/bin/${1}" -p "/usr/bin/${1}" -p "${TMP_BIN_PATH}/${1}" -p "${1}" "$@" | head -n1 + local candidate + for candidate in "/bin/${1}" "/usr/bin/${1}" "/usr/libexec/${1}" "${TMP_BIN_PATH}/${1}"; do + [ -x "$candidate" ] && { + echo "$candidate" + return 0 + } + done + + command -v "${1}" 2>/dev/null | head -n1 +} + +is_finded() { + [ -n "$(first_type "$1")" ] +} + +has_ss_rust_client_binary() { + [ -n "$(first_type sslocal)" ] +} + +has_ss_rust_server_binary() { + [ -n "$(first_type ssserver)" ] +} + +has_mihomo_binary() { + local mihomo_bin="$(first_type mihomo)" + [ -x "$mihomo_bin" ] +} + +use_mihomo_for_ss_rust_client() { + has_ss_rust_client_binary && return 1 + has_mihomo_binary +} + +use_mihomo_for_ss_rust_server() { + has_ss_rust_server_binary && return 1 + has_mihomo_binary } ln_start_bin() { @@ -187,10 +702,14 @@ ln_start_bin() { local ln_name=${2} shift 2 if [ "${file_func%%/*}" != "${file_func}" ]; then - [ ! -L "${file_func}" ] && { - ln -s "${file_func}" "${TMP_BIN_PATH}/${ln_name}" >/dev/null 2>&1 - file_func="${TMP_BIN_PATH}/${ln_name}" - } + local ln_path="${TMP_BIN_PATH}/${ln_name}" + local src_real="$(readlink -f "${file_func}" 2>/dev/null || echo "${file_func}")" + local dst_real="$(readlink -f "${ln_path}" 2>/dev/null || true)" + if [ ! -L "${ln_path}" ] || [ "${dst_real}" != "${src_real}" ]; then + rm -f "${ln_path}" + ln -s "${file_func}" "${ln_path}" >/dev/null 2>&1 + fi + file_func="${ln_path}" [ -x "${file_func}" ] || echolog "$(readlink ${file_func}) 没有执行权限,无法启动:${file_func} $*" fi #echo "${file_func} $*" >&2 @@ -204,7 +723,6 @@ ln_start_bin() { } check_run_environment() { - local prefer_nft="$(uci_get_by_type global prefer_nft 1)" local dnsmasq_info=$(dnsmasq -v 2>/dev/null) local dnsmasq_ver=$(echo "$dnsmasq_info" | sed -n '1s/.*version \([0-9.]*\).*/\1/p') @@ -212,27 +730,26 @@ check_run_environment() { DNSMASQ_NFTSET=0; [[ "$dnsmasq_info" == *" nftset"* ]] && DNSMASQ_NFTSET=1 HAS_IPT=0; { command -v iptables-legacy || command -v iptables; } >/dev/null && HAS_IPT=1 HAS_IPSET=$(command -v ipset >/dev/null && echo 1 || echo 0) + HAS_FW3=$(command -v fw3 >/dev/null && echo 1 || echo 0) HAS_FW4=$(command -v fw4 >/dev/null && echo 1 || echo 0) HAS_NFT=$(command -v nft >/dev/null && echo 1 || echo 0) # 重置 USE_TABLES USE_TABLES="" - if [ "$prefer_nft" = "1" ]; then - echolog "提示:优先使用 nftables..." - if [ "$DNSMASQ_NFTSET" -eq 1 ] && [ "$HAS_NFT" -eq 1 ] && [ "$HAS_FW4" -eq 1 ]; then + if [ "$HAS_FW4" -eq 1 ]; then + if [ "$DNSMASQ_NFTSET" -eq 1 ] && [ "$HAS_NFT" -eq 1 ]; then USE_TABLES="nftables" - elif [ "$HAS_IPSET" -eq 1 ] && [ "$HAS_IPT" -eq 1 ] && [ "$DNSMASQ_IPSET" -eq 1 ]; then - echolog "警告:nftables (fw4) 应用环境不完整,切换至 iptables。(has_fw4:$HAS_FW4/dnsmasq_nftset:$DNSMASQ_NFTSET)" - USE_TABLES="iptables" + else + echolog "警告:fw4 已检测到,但 nftables 环境不完整。(has_nft:$HAS_NFT/dnsmasq_nftset:$DNSMASQ_NFTSET)" fi - else - echolog "提示:优先使用 iptables..." + fi + + if [ -z "$USE_TABLES" ] && [ "$HAS_FW3" -eq 1 ]; then if [ "$HAS_IPSET" -eq 1 ] && [ "$HAS_IPT" -eq 1 ] && [ "$DNSMASQ_IPSET" -eq 1 ]; then USE_TABLES="iptables" - elif [ "$DNSMASQ_NFTSET" -eq 1 ] && [ "$HAS_FW4" -eq 1 ]; then - echolog "警告:iptables (fw3) 应用环境不完整,切换至 nftables。(has_ipt:$HAS_IPT/has_ipset:$HAS_IPSET/dnsmasq_ipset:$DNSMASQ_IPSET)" - USE_TABLES="nftables" + else + echolog "警告:fw3 已检测到,但 iptables 环境不完整。(has_ipset:$HAS_IPSET/has_ipt:$HAS_IPT/dnsmasq_ipset:$DNSMASQ_IPSET)" fi fi @@ -262,27 +779,113 @@ check_run_environment() { fi } +normalize_run_mode() { + local run_mode="$(uci_get_by_type global run_mode router)" + + if [ "$run_mode" = "oversea" ]; then + echolog "提示:Oversea Mode 已移除,自动切换到 IP Route Mode。" + uci_set_by_type global run_mode router + run_mode="router" + fi + + echo "${run_mode:-router}" +} + +normalize_xray_protocol_nodes() { + local changed=0 + + _migrate_xray_protocol_node() { + local section="$1" + local type="$(uci_get_by_name "$section" type)" + local v2ray_protocol="$(uci_get_by_name "$section" v2ray_protocol)" + + if [ "$type" = "ss" ] || [ "$type" = "ss-libev" ]; then + if [ -n "$(first_type mihomo)" ]; then + uci -q set "$NAME.$section.type=ss" + changed=1 + elif [ -n "$(first_type sslocal)" ]; then + uci -q set "$NAME.$section.type=ss-rust" + changed=1 + elif [ -n "$(first_type xray)" ]; then + uci -q set "$NAME.$section.type=v2ray" + uci -q set "$NAME.$section.v2ray_protocol=shadowsocks" + changed=1 + fi + elif [ "$type" = "v2ray" ] && [ "$v2ray_protocol" = "shadowsocks" ]; then + if [ -n "$(first_type mihomo)" ]; then + uci -q set "$NAME.$section.type=ss" + uci -q delete "$NAME.$section.v2ray_protocol" + changed=1 + fi + fi + if [ "$type" = "hysteria2" ]; then + echolog "提示:检测到原生 Hysteria2 节点,自动迁移为 Xray(Hysteria2)。" + uci -q set "$NAME.$section.type=v2ray" + uci -q set "$NAME.$section.v2ray_protocol=hysteria2" + changed=1 + elif [ "$type" = "trojan" ]; then + echolog "提示:检测到原生 Trojan 节点,自动迁移为 Xray(Trojan)。" + uci -q set "$NAME.$section.type=v2ray" + uci -q set "$NAME.$section.v2ray_protocol=trojan" + changed=1 + fi + } + + config_load "$NAME" + config_foreach _migrate_xray_protocol_node servers + + local subscribe_sid="$(uci_get_by_cfgid server_subscribe)" + if [ -n "$subscribe_sid" ]; then + for key in xray_hy2_type xray_tj_type ss_type; do + if uci -q get "$NAME.$subscribe_sid.$key" >/dev/null; then + uci -q delete "$NAME.$subscribe_sid.$key" + changed=1 + fi + done + fi + + if [ "$changed" = "1" ]; then + uci commit "$NAME" + fi +} + add_dns_into_ipset() { case "$1" in gfw) ipset add gfwlist ${2%:*} 2>/dev/null ;; - oversea) ipset add oversea ${2%:*} 2>/dev/null ;; *) ipset add ss_spec_wan_ac ${2%:*} nomatch 2>/dev/null ;; esac } start_dns() { local ssrplus_dns="$(uci_get_by_type global pdnsd_enable 0)" - local dnsproxy_dnsserver="$(uci_get_by_type global parse_method)" - if [ -n "$dnsproxy_dnsserver" ] && [ "$dnsproxy_dnsserver" != "parse_file" ]; then - dnsserver="$(uci_get_by_type global dnsproxy_tunnel_forward 8.8.4.4:53)" - elif [ -n "$ssrplus_dns" ] && [ "$ssrplus_dns" = "6" ]; then + local global_server_type="$(uci_get_by_name "$GLOBAL_SERVER" type)" + local builtin_dns_enabled=0 + local dnsserver="$(uci_get_by_type global tunnel_forward 8.8.4.4:53)" + if [ "$ssrplus_dns" = "5" ]; then + ssrplus_dns="$(force_dns2tcp_fallback "旧版 DNSPROXY 模式已移除")" || return 1 + uci_set_by_type global pdnsd_enable "$ssrplus_dns" + elif [ "$ssrplus_dns" = "6" ]; then dnsserver="$(uci_get_by_type global chinadns_ng_tunnel_forward 8.8.4.4:53)" - else - dnsserver="$(uci_get_by_type global tunnel_forward 8.8.4.4:53)" fi - local run_mode="$(uci_get_by_type global run_mode)" + local run_mode="$(normalize_run_mode)" - if [ "$ssrplus_dns" != "0" ]; then + if [ "$ssrplus_dns" = "4" ] && ! is_finded "mosdns"; then + ssrplus_dns="$(force_dns2tcp_fallback "当前 DNS 模式 MosDNS 不可用")" || return 1 + uci_set_by_type global pdnsd_enable "$ssrplus_dns" + fi + + if [ "$ssrplus_dns" = "7" ]; then + if supports_builtin_dns "$global_server_type"; then + builtin_dns_enabled=1 + else + ssrplus_dns="$(force_dns2tcp_fallback "当前主节点不支持模块内置 DNS")" || return 1 + uci_set_by_type global pdnsd_enable "$ssrplus_dns" + fi + fi + + if [ "$builtin_dns_enabled" = "1" ]; then + pdnsd_enable_flag=7 + elif [ "$ssrplus_dns" != "0" ]; then if [ "$HAS_IPSET" -eq 1 ]; then if [ -n "$dnsserver" ]; then add_dns_into_ipset $run_mode $dnsserver @@ -290,22 +893,11 @@ start_dns() { fi case "$ssrplus_dns" in 1) - ln_start_bin $(first_type dns2tcp) dns2tcp -L 127.0.0.1#$dns_port -R ${dnsserver/:/#} + start_dns2tcp "$dnsserver" pdnsd_enable_flag=1 ;; - 2) - ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_dns_port ssrplus-dns - ln_start_bin $(first_type dns2socks) dns2socks 127.0.0.1:$tmp_dns_port $dnsserver 127.0.0.1:$dns_port -q - pdnsd_enable_flag=2 - ;; - 3) - ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_dns_port ssrplus-dns - ln_start_bin $(first_type dns2socks-rust) dns2socks-rust -s socks5://127.0.0.1:$tmp_dns_port -d $dnsserver -l 127.0.0.1:$dns_port -f -c - echolog "DNS2SOCKS Rust query and cache Started!" - pdnsd_enable_flag=3 - ;; 4) - local mosdns_ipv6="$(uci_get_by_type global mosdns_ipv6)" + local filter_aaaa="$(get_filter_aaaa)" local mosdns_dnsserver="$(uci_get_by_type global tunnel_forward_mosdns)" output=$(for i in $(echo $mosdns_dnsserver | sed "s/,/ /g"); do dnsserver=${i%:*} @@ -318,7 +910,7 @@ start_dns() { done) awk -v line=14 -v text="$output" 'NR == line+1 {print text} 1' /etc/ssrplus/mosdns-config.yaml | sed "s/DNS_PORT/$dns_port/g" | sed "s/\(concurrent:\).*/\1 $(echo "$mosdns_dnsserver" | sed 's/,/ /g' | wc -w)/g"> $TMP_PATH/mosdns-config.yaml - if [ "$mosdns_ipv6" == "0" ]; then + if [ "$filter_aaaa" == "0" ]; then sed -i "s/DNS_MODE/main_sequence_with_IPv6/g" $TMP_PATH/mosdns-config.yaml else sed -i "s/DNS_MODE/main_sequence_disable_IPv6/g" $TMP_PATH/mosdns-config.yaml @@ -326,65 +918,6 @@ start_dns() { ln_start_bin $(first_type mosdns) mosdns start -c $TMP_PATH/mosdns-config.yaml pdnsd_enable_flag=4 ;; - 5) - dnsproxy_ipv6="$(uci_get_by_type global dnsproxy_ipv6)" - if [ "$dnsproxy_ipv6" -eq "1" ]; then - disabled_ipv6="--ipv6-disabled" - fi - if [ "$dnsproxy_dnsserver" != "parse_file" ]; then - ln_start_bin $(first_type dnsproxy) dnsproxy -l 127.0.0.1 -p $tmp_dns_port -p $dns_port -u $dnsserver $disabled_ipv6 --cache --cache-min-ttl=3600 - else - dnsproxy_dnsserver_file="$TMP_PATH/dnsproxy_dns.list" - cleaned_file="$TMP_PATH/cleaned_dns.list" - temp_file="$TMP_PATH/temp_dns.list" - > "$cleaned_file" - # 清理输入文件并去重 - while IFS= read -r line || [ -n "$line" ]; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - [ -z "$line" ] && continue - echo "$line" | grep -qE '^#' && continue - echo "$line" >> "$cleaned_file" - done < "/etc/ssrplus/dnsproxy_dns.list" - # 获取清理后文件的MD5 - cleaned_md5=$(md5sum "$cleaned_file" | awk '{print $1}') - if [ ! -f "$dnsproxy_dnsserver_file" ]; then - cp "$cleaned_file" "$dnsproxy_dnsserver_file" - else - target_md5=$(md5sum "$dnsproxy_dnsserver_file" | awk '{print $1}') - if [ "$cleaned_md5" != "$target_md5" ]; then - > "$temp_file" - # 保留目标文件中也存在于清理文件的记录(去重) - while IFS= read -r line; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - if grep -qixF "$line" "$cleaned_file" && ! grep -qixF "$line" "$temp_file"; then - echo "$line" >> "$temp_file" - fi - done < "$dnsproxy_dnsserver_file" - # 添加清理文件中有但目标文件没有的记录(去重) - while IFS= read -r line; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - if ! grep -qixF "$line" "$temp_file"; then - echo "$line" >> "$temp_file" - fi - done < "$cleaned_file" - temp_md5=$(md5sum "$temp_file" | awk '{print $1}') - if [ "$temp_md5" != "$target_md5" ]; then - mv "$temp_file" "$dnsproxy_dnsserver_file" - else - rm -f "$temp_file" - fi - fi - fi - rm -f "$cleaned_file" - - if [ -n "$dnsproxy_dnsserver_file" ] && [ -s "$dnsproxy_dnsserver_file" ]; then - local upstreams_logic_mode="$(uci_get_by_type global upstreams_logic_mode)" - ln_start_bin $(first_type dnsproxy) dnsproxy -l 127.0.0.1 -p $tmp_dns_port -p $dns_port -u $dnsproxy_dnsserver_file $disabled_ipv6 --cache --cache-min-ttl=3600 --upstream-mode=$upstreams_logic_mode - fi - fi - echolog "DNSPROXY query and cache Started!" - pdnsd_enable_flag=5 - ;; 6) local chinadns_ng_proto="$(uci_get_by_type global chinadns_ng_proto)" local chinadns_ng_dns="" @@ -448,12 +981,134 @@ start_dns() { sed -i "s,$(printf '%s' "$old_appledns"),$(printf '%s' "$new_appledns"),g" /etc/ssrplus/applechina.conf fi fi - echolog "Apple 域名中国大陆 CDN 的 优化规则正在加载。" cp -f /etc/ssrplus/applechina.conf $TMP_DNSMASQ_PATH/ - echolog "Apple 域名中国大陆 CDN 的 优化规则加载完毕。" fi } +# 生成 Xray 服务端配置 +# 生成 Xray 服务端配置 +generate_xray_config() { + local type="$1" + local port="$2" + local password="$3" + local security="$4" + local method="$5" + local network="$6" + local ws_path="$7" + local ws_host="$8" + local grpc_service="$9" + local alter_id="${10}" + local tcp_guise="${11}" + local tcp_guise_http_host="${12}" + local tcp_guise_http_path="${13}" + local config_file="${14}" + + local inbound_json="" + + case "$type" in + vmess) + inbound_json="{ + \"protocol\": \"vmess\", + \"port\": $port, + \"settings\": { + \"clients\": [{\"id\": \"$password\", \"alterId\": $alter_id}] + }, + \"streamSettings\": { + \"network\": \"$network\" + } + }" + ;; + vless) + inbound_json="{ + \"protocol\": \"vless\", + \"port\": $port, + \"settings\": { + \"clients\": [{\"id\": \"$password\"}], + \"decryption\": \"none\" + }, + \"streamSettings\": { + \"network\": \"$network\" + } + }" + ;; + trojan) + inbound_json="{ + \"protocol\": \"trojan\", + \"port\": $port, + \"settings\": { + \"clients\": [{\"password\": \"$password\"}] + }, + \"streamSettings\": { + \"network\": \"$network\" + } + }" + ;; + shadowsocks) + inbound_json="{ + \"protocol\": \"shadowsocks\", + \"port\": $port, + \"settings\": { + \"method\": \"$method\", + \"password\": \"$password\" + } + }" + ;; + *) + return 1 + ;; + esac + + # 添加传输层细节 + case "$network" in + ws) + local ws_json="{\"path\": \"$ws_path\"}" + [ -n "$ws_host" ] && ws_json="{\"path\": \"$ws_path\", \"headers\": {\"Host\": \"$ws_host\"}}" + inbound_json=$(echo "$inbound_json" | sed "s/\"streamSettings\": {/\"streamSettings\": {\"wsSettings\": $ws_json, /") + ;; + grpc) + [ -n "$grpc_service" ] && inbound_json=$(echo "$inbound_json" | sed "s/\"streamSettings\": {/\"streamSettings\": {\"grpcSettings\": {\"serviceName\": \"$grpc_service\"}, /") + ;; + tcp) + # TCP HTTP 伪装 + if [ "$tcp_guise" = "http" ]; then + local tcp_json="{\"header\": {\"type\": \"http\"" + + # 添加 HTTP Host + if [ -n "$tcp_guise_http_host" ]; then + # 将空格分隔的多个 host 转换为 JSON 数组格式 + local host_list=$(echo "$tcp_guise_http_host" | sed 's/ /","/g') + tcp_json="$tcp_json, \"request\": {\"headers\": {\"Host\": [\"$host_list\"]}}" + fi + + # 添加 HTTP Path + if [ -n "$tcp_guise_http_path" ]; then + local path_list=$(echo "$tcp_guise_http_path" | sed 's/ /","/g') + tcp_json="$tcp_json, \"response\": {\"headers\": {\"Location\": [\"$path_list\"]}}" + fi + + tcp_json="$tcp_json}}" + inbound_json=$(echo "$inbound_json" | sed "s/\"streamSettings\": {/\"streamSettings\": {\"tcpSettings\": $tcp_json, /") + fi + ;; + esac + + cat > "$config_file" << EOF +{ + "log": { + "loglevel": "warning" + }, + "inbounds": [$inbound_json], + "outbounds": [ + { + "protocol": "freedom", + "settings": {} + } + ] +} +EOF + return 0 +} + gen_service_file() { #1-server.type 2-cfgname 3-file_path local fastopen if [ $(uci_get_by_name $2 fast_open) == "1" ]; then @@ -595,50 +1250,48 @@ gen_config_file() { #server1 type2 code3 local_port4 socks_port5 chain6 threads5 ;; esac ;; - socks5) - /usr/share/shadowsocksr/genred2config.sh $config_file $2 $mode $4 \ - "$(uci_get_by_name $1 server)" \ - "$(uci_get_by_name $1 server_port)" \ - "$(uci_get_by_name $1 auth_enable 0)" \ - "$(uci_get_by_name $1 username)" \ - "$(uci_get_by_name $1 password)" - ;; - tun) - /usr/share/shadowsocksr/genred2config.sh $config_file $2 $(uci_get_by_name $1 iface "br-lan") $4 - ;; esac sed -i 's/\\//g' $TMP_PATH/*-ssr-*.json #>/dev/null > 2>&1 } start_udp() { local udp_relay_server_type=$(uci_get_by_name $UDP_RELAY_SERVER type) + local threads=$(get_configured_threads) local type=$udp_relay_server_type - if [ "$udp_relay_server_type" = "ss-rust" ] || [ "$udp_relay_server_type" = "ss-libev" ]; then + if [ "$udp_relay_server_type" = "ss-rust" ]; then type="ss" fi redir_udp=1 case "$type" in ss | ssr) - gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port - if [ "$udp_relay_server_type" = "ss-libev" ] || [ "$udp_relay_server_type" = "ssr" ]; then - ss_program="$(first_type ${type}-redir)" - elif [ "$udp_relay_server_type" = "ss-rust" ]; then - ss_program="$(first_type ${type}local)" + if [ "$udp_relay_server_type" = "ss" ]; then + redir_udp=0 + ARG_UDP="" + echolog "UDP TPROXY Relay:Mihomo 单节点由主实例处理。" + elif [ "$udp_relay_server_type" = "ss-rust" ] && use_mihomo_for_ss_rust_client; then + redir_udp=0 + ARG_UDP="" + echolog "UDP TPROXY Relay:ss-rust 主程序不存在,已回退到 Mihomo 主实例处理。" + else + gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port + if [ "$udp_relay_server_type" = "ssr" ]; then + ss_program="$(first_type ${type}-redir)" + elif [ "$udp_relay_server_type" = "ss-rust" ] || [ "$udp_relay_server_type" = "ss" ]; then + ss_program="$(first_type ${type}local)" + fi + echolog "$(get_name $type) program is: $ss_program" + old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null) + if [ "$old_ss_program" != "$ss_program" ]; then + rm -rf "$TMP_PATH/bin/${type}-redir" + fi + ln_start_bin $ss_program ${type}-redir -c $udp_config_file + echolog "UDP TPROXY Relay:$(get_name $type) Started!" fi - echolog "$(get_name $type) program is: $ss_program" - # 获取当前软链接指向的执行文件路径 - old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null) - # **当新旧执行文件路径不同时,删除旧链接** - if [ "$old_ss_program" != "$ss_program" ]; then - rm -rf "$TMP_PATH/bin/${type}-redir" - fi - ln_start_bin $ss_program ${type}-redir -c $udp_config_file - echolog "UDP TPROXY Relay:$(get_name $type) Started!" ;; v2ray) gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port - ln_start_bin $(first_type xray v2ray) v2ray run -c $udp_config_file - echolog "UDP TPROXY Relay:$($(first_type "xray" "v2ray") version | head -1) Started!" + ln_start_bin $(first_type xray) v2ray run -c $udp_config_file + echolog "UDP TPROXY Relay:$($(first_type xray) version | head -1) Started!" ;; trojan) #client gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_local_port @@ -651,21 +1304,11 @@ start_udp() { redir_udp=0 ARG_UDP="" ;; - hysteria2) - gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port - ln_start_bin $(first_type hysteria) hysteria client --config $udp_config_file - echolog "UDP TPROXY Relay:$($(first_type "hysteria") version | grep Version | awk '{print "Hysteria2: " $2}') Started!" - ;; tuic) - # FIXME: ipt2socks cannot handle udp reply from tuic - # 20230726 uncomment following 4 lines - gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_local_port - ln_start_bin $(first_type tuic-client) tuic-client --config $udp_config_file - ln_start_bin $(first_type ipt2socks) ipt2socks -U -b 0.0.0.0 -4 -s 127.0.0.1 -p $tmp_udp_local_port -l $tmp_udp_port - echolog "UDP TPROXY Relay:$($(first_type tuic-client) --version) Started!" - echolog "TUIC UDP TPROXY Relay not supported!" - #redir_udp=0 - #ARG_UDP="" + # TUIC now uses Mihomo directly and should follow the native path. + redir_udp=0 + ARG_UDP="" + echolog "TUIC UDP relay is handled by the main Mihomo instance." ;; shadowtls) gen_config_file $UDP_RELAY_SERVER $type 2 ${tmp_udp_local_port} @@ -674,7 +1317,7 @@ start_udp() { local chain_type=$(uci_get_by_name $UDP_RELAY_SERVER chain_type) case ${chain_type} in vmess) - ln_start_bin $(first_type xray v2ray) v2ray run -c $udp_config_file + ln_start_bin $(first_type xray) v2ray run -c $udp_config_file echolog "UDP TPROXY Relay:shadow-tls chain-to $($(first_type xray) --version) Started!" ;; sslocal) @@ -684,366 +1327,75 @@ start_udp() { esac ;; socks5) - # if [ "$(uci_get_by_name $UDP_RELAY_SERVER auth_enable 0)" == "1" ]; then - # local auth="-a $(uci_get_by_name $UDP_RELAY_SERVER username) -k $(uci_get_by_name $UDP_RELAY_SERVER password)" - # fi - # ln_start_bin $(first_type ipt2socks) ipt2socks $udp_config_file -U -4 -s $(uci_get_by_name $UDP_RELAY_SERVER server) -p $(uci_get_by_name $UDP_RELAY_SERVER server_port) -l $tmp_udp_port $auth - gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port - ln_start_bin $(first_type redsocks2) redsocks2 -c $udp_config_file - echolog "UDP TPROXY Relay:Socks5 REDIRECT/TPROXY Started!" - ;; - tun) - echolog "Network Tunnel UDP TPROXY Relay not supported!" - redir_udp=0 - ARG_UDP="" - ;; - esac -} - -shunt_dns_command() { - local shunt_dns_mode="$(uci_get_by_type global shunt_dns_mode)" - local shunt_dnsproxy_dnsserver="$(uci_get_by_type global shunt_parse_method)" - if [ -n "$shunt_dnsproxy_dnsserver" ] && [ "$shunt_dnsproxy_dnsserver" != "parse_file" ]; then - shunt_dnsserver="$(uci_get_by_type global dnsproxy_shunt_forward 8.8.4.4:53)" - elif [ -n "shunt_dns_mode" ] && [ "$shunt_dns_mode" = "5" ]; then - shunt_dnsserver="$(uci_get_by_type global chinadns_ng_shunt_dnsserver 8.8.4.4:53)" - else - shunt_dnsserver="$(uci_get_by_type global shunt_dnsserver 8.8.4.4:53)" - fi - local tmp_port=$1 - case "$shunt_dns_mode" in - 1) - ln_start_bin $(first_type dns2socks) dns2socks 127.0.0.1:$tmp_port $shunt_dnsserver 127.0.0.1:$tmp_shunt_dns_port -q - ;; - 2) - ln_start_bin $(first_type dns2socks-rust) dns2socks-rust -s socks5://127.0.0.1:$tmp_port -d $shunt_dnsserver -l 127.0.0.1:$tmp_shunt_dns_port -f -c - echolog "DNS2SOCKS Rust Shunt query Started!" - ;; - 3) - local shunt_mosdns_ipv6="$(uci_get_by_type global shunt_mosdns_ipv6)" - local shunt_mosdns_dnsserver="$(uci_get_by_type global shunt_mosdns_dnsserver)" - output=$(for i in $(echo $shunt_mosdns_dnsserver | sed "s/,/ /g"); do - echo " - addr: $i" - echo " socks5: \"127.0.0.1:$tmp_port\"" - echo " enable_pipeline: true" - done) - awk -v line=14 -v text="$output" 'NR == line+1 {print text} 1' /etc/ssrplus/mosdns-config.yaml | sed "s/DNS_PORT/$tmp_shunt_dns_port/g" | sed "s/\(concurrent:\).*/\1 $(echo "$mosdns_dnsserver" | sed 's/,/ /g' | wc -w)/g" > $TMP_PATH/mosdns-config-shunt.yaml - - if [ "$shunt_mosdns_ipv6" == "0" ]; then - sed -i "s/DNS_MODE/main_sequence_with_IPv6/g" $TMP_PATH/mosdns-config-shunt.yaml - else - sed -i "s/DNS_MODE/main_sequence_disable_IPv6/g" $TMP_PATH/mosdns-config-shunt.yaml + if [ "$(uci_get_by_name $UDP_RELAY_SERVER auth_enable 0)" == "1" ]; then + local auth="-a $(uci_get_by_name $UDP_RELAY_SERVER username) -k $(uci_get_by_name $UDP_RELAY_SERVER password)" fi - ln_start_bin $(first_type mosdns) mosdns start -c $TMP_PATH/mosdns-config-shunt.yaml - ;; - 4) - shunt_dnsproxy_ipv6="$(uci_get_by_type global shunt_dnsproxy_ipv6)" - if [ "$shunt_dnsproxy_ipv6" -eq "1" ]; then - shunt_disabled_ipv6="--ipv6-disabled" - fi - if [ "$shunt_dnsproxy_dnsserver" != "parse_file" ]; then - ln_start_bin $(first_type dnsproxy) dnsproxy -l 127.0.0.1 -p $tmp_port -p $tmp_shunt_dns_port -u $shunt_dnsserver $shunt_disabled_ipv6 --cache --cache-min-ttl=3600 - else - shunt_dnsproxy_dnsserver_file="$TMP_PATH/dnsproxy_dns.list" - cleaned_file="$TMP_PATH/cleaned_dns_servers.list" - temp_file="$TMP_PATH/temp_dns_servers.list" - > "$cleaned_file" - # 清理输入文件并去重 - while IFS= read -r line || [ -n "$line" ]; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - [ -z "$line" ] && continue - echo "$line" | grep -qE '^#' && continue - echo "$line" >> "$cleaned_file" - done < "/etc/ssrplus/dnsproxy_dns.list" - # 获取清理后文件的MD5 - cleaned_md5=$(md5sum "$cleaned_file" | awk '{print $1}') - if [ ! -f "$shunt_dnsproxy_dnsserver_file" ]; then - cp "$cleaned_file" "$shunt_dnsproxy_dnsserver_file" - else - target_md5=$(md5sum "$shunt_dnsproxy_dnsserver_file" | awk '{print $1}') - if [ "$cleaned_md5" != "$target_md5" ]; then - > "$temp_file" - # 保留目标文件中也存在于清理文件的记录(去重) - while IFS= read -r line; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - if grep -qixF "$line" "$cleaned_file" && ! grep -qixF "$line" "$temp_file"; then - echo "$line" >> "$temp_file" - fi - done < "$shunt_dnsproxy_dnsserver_file" - # 添加清理文件中有但目标文件没有的记录(去重) - while IFS= read -r line; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - if ! grep -qixF "$line" "$temp_file"; then - echo "$line" >> "$temp_file" - fi - done < "$cleaned_file" - temp_md5=$(md5sum "$temp_file" | awk '{print $1}') - if [ "$temp_md5" != "$target_md5" ]; then - mv "$temp_file" "$shunt_dnsproxy_dnsserver_file" - else - rm -f "$temp_file" - fi - fi - fi - rm -f "$cleaned_file" - - if [ -n "$shunt_dnsproxy_dnsserver_file" ] && [ -s "$shunt_dnsproxy_dnsserver_file" ]; then - local shunt_upstreams_logic_mode="$(uci_get_by_type global shunt_upstreams_logic_mode)" - ln_start_bin $(first_type dnsproxy) dnsproxy -l 127.0.0.1 -p $tmp_port -p $tmp_shunt_dns_port -u $shunt_dnsproxy_dnsserver_file $shunt_disabled_ipv6 --cache --cache-min-ttl=3600 --upstream-mode=$shunt_upstreams_logic_mode - fi - fi - echolog "DNSPROXY shunt query and cache Started!" - ;; - 5) - local chinadns_ng_shunt_proto="$(uci_get_by_type global chinadns_ng_shunt_proto)" - local chinadns_ng_shunt_dns="" - # 遍历每个 DNS 服务器 - IFS=',' # 设置分隔符为逗号 - for chinadns_ng_shunt_server in $shunt_dnsserver; do - # 处理单个服务器地址 - local chinadns_ng_shunt_ip="${chinadns_ng_shunt_server%%:*}" - local chinadns_ng_shunt_port="${chinadns_ng_shunt_server##*:}" - [ "$chinadns_ng_shunt_ip" = "$chinadns_ng_shunt_port" ] && chinadns_ng_shunt_port="53" - chinadns_ng_shunt_tls_port="853" - # 根据协议类型格式化服务器地址 - case "$chinadns_ng_shunt_proto" in - "none") - chinadns_ng_shunt_server="${chinadns_ng_shunt_ip}#${chinadns_ng_shunt_port}" - ;; - "tls") - chinadns_ng_shunt_server="${chinadns_ng_shunt_proto}://${chinadns_ng_shunt_ip}#${chinadns_ng_shunt_tls_port}" - ;; - *) - chinadns_ng_shunt_server="${chinadns_ng_shunt_proto}://${chinadns_ng_shunt_ip}#${chinadns_ng_shunt_port}" - ;; - esac - # 添加到参数列表 - chinadns_ng_shunt_dns="${chinadns_ng_shunt_dns} -t ${chinadns_ng_shunt_server}" + for i in $(seq 1 $threads); do + ln_start_bin $(first_type ipt2socks) ipt2socks -U -r -b 0.0.0.0 -4 \ + -s $(uci_get_by_name $UDP_RELAY_SERVER server) \ + -p $(uci_get_by_name $UDP_RELAY_SERVER server_port) \ + -l $tmp_udp_port $auth done - unset IFS # 恢复默认分隔符 - shunt_dnsserver="$chinadns_ng_shunt_dns" - # 启动 chinadns-ng - ln_start_bin $(first_type chinadns-ng) chinadns-ng -b 127.0.0.1 -l $tmp_port -l $tmp_shunt_dns_port -p 3 -d gfw $shunt_dnsserver -N --filter-qtype 64,65 -f -r --cache 4096 --cache-stale 86400 --cache-refresh 20 - echolog "ChinaDNS-NG shunt query and cache Started!" + echolog "UDP TPROXY Relay:Socks5 via IPT2Socks $threads Threads Started!" ;; esac } -shunt_dns_config_file_port() { - if [ "$LOCAL_SERVER" == "$SHUNT_SERVER" ]; then - # NetFlix 和 全局socks 节点相同 - if [ "$(uci_get_by_type socks5_proxy socks5_auth nil)" != "noauth" ]; then - # 全局socks 有密码,NetFlix 不能使用 auth 验证,需更换为新端口并使用无密码的 socks 配置用于分流 - # 新增NetFlix dns 使用端口 - local port=$tmp_shunt_local_port - jq --arg port "$port" '.inbounds |= .[0:1] + [{"protocol":"socks","port":($port | tonumber),"settings":{"udp":true,"auth":"noauth"}}] + .[1:]' "$shunt_config_file" > "$shunt_config_file.tmp" && mv "$shunt_config_file.tmp" $shunt_config_file - echo $port # 返回端口号 - return 0 # 成功返回 - else - sed -i -e '/"mixed"/d' $shunt_config_file - fi - else - # NetFlix 和 全局 socks 节点不相同 - if [ "$(uci_get_by_type socks5_proxy socks5_auth nil)" != "noauth" ]; then - # 全局socks 有密码,NetFlix不能使用auth验证,需设置为无密码的socks配置用于分流 - # 删除 NetFlix dns 端口密码验证 - sed -i \ - -e '/"mixed"/d' \ - -e 's/"auth"\s*:\s*"password"/\"auth\": \"noauth\"/g' \ - -e '/"accounts": \[/,/\]/d' $shunt_config_file - else - sed -i -e '/"mixed"/d' $shunt_config_file - fi - fi - # 使用传入的端口 - echo $1 # 返回传入的端口号 - return 0 # 成功返回 -} - -start_shunt() { - local shunt_server_type=$(uci_get_by_name $SHUNT_SERVER type) - local type=$shunt_server_type - if [ "$shunt_server_type" = "ss-rust" ] || [ "$shunt_server_type" = "ss-libev" ]; then - type="ss" - fi - case "$type" in - ss | ssr) - gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port - if [ "$shunt_server_type" = "ss-libev" ] || [ "$shunt_server_type" = "ssr" ]; then - ss_program="$(first_type ${type}-redir)" - elif [ "$shunt_server_type" = "ss-rust" ]; then - ss_program="$(first_type ${type}local)" - fi - echolog "$(get_name $type) program is: $ss_program" - # 获取当前软链接指向的执行文件路径 - old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null) - # **当新旧执行文件路径不同时,删除旧链接** - if [ "$old_ss_program" != "$ss_program" ]; then - rm -rf "$TMP_PATH/bin/${type}-redir" - fi - ln_start_bin $ss_program ${type}-redir -c $shunt_config_file - if [ -n "$tmp_local_port" ]; then - local tmp_port=$tmp_local_port - else - local tmp_port=$tmp_shunt_local_port - if [ "$shunt_server_type" = "ss-libev" ] || [ "$shunt_server_type" = "ssr" ]; then - dns_ss_program="$(first_type ${type}-local)" - elif [ "$shunt_server_type" = "ss-rust" ]; then - dns_ss_program="$(first_type ${type}local)" - fi - # 获取当前软链接指向的执行文件路径 - old_dns_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-local" 2>/dev/null) - if [ "$old_dns_ss_program" != "$dns_ss_program" ]; then - rm -rf "$TMP_PATH/bin/${type}-local" - fi - ln_start_bin $dns_ss_program ${type}-local -c $shunt_dns_config_file - fi - shunt_dns_command $tmp_port - echolog "shunt:$(get_name $type) Started!" - ;; - v2ray) - local tmp_port=${tmp_local_port:-$tmp_shunt_local_port} - gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port $tmp_port - # 处理配置文件中的 NetFlix 端口 - tmp_port=$(shunt_dns_config_file_port $tmp_port) - ln_start_bin $(first_type xray v2ray) v2ray run -c $shunt_config_file - shunt_dns_command $tmp_port - echolog "shunt:$($(first_type xray v2ray) version | head -1) Started!" - ;; - trojan) - gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port - ln_start_bin $(first_type trojan) $type --config $shunt_config_file - if [ -n "$tmp_local_port" ]; then - local tmp_port=$tmp_local_port - else - local tmp_port=$tmp_shunt_local_port - ln_start_bin $(first_type trojan) $type --config $shunt_dns_config_file - fi - shunt_dns_command $tmp_port - echolog "shunt:$($(first_type trojan) --version 2>&1 | head -1) Started!" - ;; - naiveproxy) - gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port - ln_start_bin $(first_type naive) naive --config $shunt_config_file - if [ -n "$tmp_local_port" ]; then - local tmp_port=$tmp_local_port - else - local tmp_port=$tmp_shunt_local_port - ln_start_bin $(first_type naive) naive --config $shunt_dns_config_file - fi - shunt_dns_command $tmp_port - echolog "shunt:$($(first_type "naive") --version 2>&1 | head -1) Started!" - redir_udp=0 - ;; - hysteria2) - if [ -n "$tmp_local_port" ]; then - local tmp_port=$tmp_local_port - gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port - else - local tmp_port=$tmp_shunt_local_port - gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port $tmp_port - fi - ln_start_bin $(first_type hysteria) hysteria client --config $shunt_config_file - shunt_dns_command $tmp_port - echolog "shunt:$($(first_type hysteria) version | grep Version | awk '{print "Hysteria2: " $2}') Started!" - ;; - tuic) - local chain_shunt_port="30${tmp_shunt_port}" - gen_config_file $SHUNT_SERVER $type 3 $chain_shunt_port 0 chain #make a tuic socks:30303, make a ipt2socks redir:303 - ln_start_bin $(first_type tuic-client) tuic-client --config $shunt_config_file - ln_start_bin $(first_type ipt2socks) ipt2socks -R -b 0.0.0.0 -4 -s 127.0.0.1 -p $chain_shunt_port -l $tmp_shunt_port - - [ -n "$tmp_local_port" ] && tmp_port=$tmp_local_port || tmp_port=$tmp_shunt_local_port - gen_config_file $SHUNT_SERVER $type 3 $tmp_port # make a tuic socks :304 - ln_start_bin $(first_type tuic-client) tuic-client --config $shunt_dns_config_file - shunt_dns_command $tmp_port - echolog "Netflix Separated Shunt Server:$($(first_type tuic-client) --version) Started!" - # FIXME: ipt2socks cannot handle udp reply from tuic - #redir_udp=0 - ;; - shadowtls) - [ -n "$tmp_local_port" ] && tmp_port=$tmp_local_port || tmp_port=$tmp_shunt_local_port - gen_config_file $SHUNT_SERVER $type 3 "10${tmp_shunt_port}" $tmp_port chain/$tmp_shunt_port #make a redir:303 and a socks:304 - #echo "debug \$tmp_port=$tmp_port, \$tmp_shunt_port=${tmp_shunt_port}, \$tmp_shunt_local_port=$tmp_shunt_local_port" - ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file - shunt_dns_command $tmp_port - local chain_type=$(uci_get_by_name $SHUNT_SERVER chain_type) - case ${chain_type} in - vmess) - ln_start_bin $(first_type xray v2ray) v2ray run -c $shunt_config_file - echolog "Netflix Separated Shunt Server:shadow-tls chain-to$($(first_type xray) --version) Started!" - ;; - sslocal) - ln_start_bin $(first_type sslocal) sslocal -c $shunt_config_file - echolog "Netflix Separated Shunt Server:shadow-tls chain-to$($(first_type sslocal) --version) Started!" - ;; - esac - ;; - # socks5) - # if [ "$(uci_get_by_name $SHUNT_SERVER auth_enable 0)" == "1" ]; then - # local auth="-a $(uci_get_by_name $SHUNT_SERVER username) -k $(uci_get_by_name $SHUNT_SERVER password)" - # fi - # ln_start_bin $(first_type ipt2socks) ipt2socks $shunt_config_file -R -4 -s $(uci_get_by_name $SHUNT_SERVER server) -p $(uci_get_by_name $SHUNT_SERVER server_port) -l $tmp_shunt_port $auth - # #gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port - # #ln_start_bin $(first_type redsocks2) redsocks2 -c $shunt_config_file - # if [ -n "$tmp_local_port" ]; then - # local tmp_port=$tmp_local_port - # else - # local tmp_port=$tmp_shunt_local_port - # ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_port shunt-dns-ssr-plus - # fi - # shunt_dns_command $tmp_port - # echolog "shunt:$type REDIRECT/TPROXY Started!" - # ;; - *) - gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port - ln_start_bin $(first_type redsocks2) redsocks2 -c $shunt_config_file - if [ -n "$tmp_local_port" ]; then - local tmp_port=$tmp_local_port - else - local tmp_port=$tmp_shunt_local_port - ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_port shunt-dns-ssr-plus - fi - shunt_dns_command $tmp_port - echolog "shunt:$type REDIRECT/TPROXY Started!" - ;; - esac - return 0 -} - start_local() { [ "$LOCAL_SERVER" = "nil" ] && return 1 - local local_port=$(uci_get_by_type socks5_proxy local_port) - [ "$LOCAL_SERVER" == "$SHUNT_SERVER" ] && tmp_local_port=$local_port + local local_port="${1:-$(uci_get_by_type socks5_proxy local_port)}" local local_server_type=$(uci_get_by_name $LOCAL_SERVER type) + local probe_instance_key="$(printf '%s' "${SSR_SWITCH_PROBE_INSTANCE_KEY:-}" | tr -cd '0-9A-Za-z._-')" local type=$local_server_type - if [ "$local_server_type" = "ss-rust" ] || [ "$local_server_type" = "ss-libev" ]; then + if [ "$local_server_type" = "ss-rust" ]; then type="ss" fi case "$type" in ss | ssr) - gen_config_file $LOCAL_SERVER $type 4 $local_port - if [ "$local_server_type" = "ss-libev" ] || [ "$local_server_type" = "ssr" ]; then + if [ "$local_server_type" = "ssr" ]; then + gen_config_file $LOCAL_SERVER $type 4 $local_port ss_program="$(first_type ${type}-local)" - elif [ "$local_server_type" = "ss-rust" ]; then - ss_program="$(first_type ${type}local)" + echolog "$(get_name $type) program is: $ss_program" + old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-local" 2>/dev/null) + if [ "$old_ss_program" != "$ss_program" ]; then + rm -rf "$TMP_PATH/bin/${type}-local" + fi + ln_start_bin $ss_program ${type}-local -c $local_config_file + echolog "Global_Socks5:$(get_name $type) Started!" + elif [ "$local_server_type" = "ss" ] || { [ "$local_server_type" = "ss-rust" ] && use_mihomo_for_ss_rust_client; }; then + local mihomo_bin="$(first_type mihomo)" + local instance_key="${LOCAL_SERVER}-local" + [ -n "$probe_instance_key" ] && instance_key="$probe_instance_key" + [ -x "$mihomo_bin" ] || { + echolog "Global_Socks5:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。" + return 1 + } + prepare_ss_mihomo_runtime_config "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" || return 1 + local ss_workdir="$(get_ss_mihomo_workdir "$instance_key")" + ln_start_bin "$mihomo_bin" ss-local -d "$ss_workdir" -f "$ss_workdir/config.yaml" + if [ "$local_server_type" = "ss-rust" ]; then + echolog "Global_Socks5:ss-rust 主程序不存在,已回退到 Mihomo (Shadowsocks) 启动。" + else + echolog "Global_Socks5:Mihomo (Shadowsocks) Started!" + fi + else + gen_config_file $LOCAL_SERVER $type 4 $local_port + ss_program="$(first_type sslocal)" + echolog "ShadowSocks program is: $ss_program" + old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-local" 2>/dev/null) + if [ "$old_ss_program" != "$ss_program" ]; then + rm -rf "$TMP_PATH/bin/${type}-local" + fi + ln_start_bin $ss_program ${type}-local -c $local_config_file + echolog "Global_Socks5:Shadowsocks-rust Started!" fi - echolog "$(get_name $type) program is: $ss_program" - # 获取当前软链接指向的执行文件路径 - old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-local" 2>/dev/null) - # **当 新旧执行文件路径不同时,删除旧链接** - if [ "$old_ss_program" != "$ss_program" ]; then - rm -rf "$TMP_PATH/bin/${type}-local" - fi - ln_start_bin $ss_program ${type}-local -c $local_config_file - echolog "Global_Socks5:$(get_name $type) Started!" ;; v2ray) if [ "$_local" == "2" ]; then gen_config_file $LOCAL_SERVER $type 4 0 $local_port - ln_start_bin $(first_type xray v2ray) v2ray run -c $local_config_file + ln_start_bin $(first_type xray) v2ray run -c $local_config_file fi - echolog "Global_Socks5:$($(first_type "xray" "v2ray") version | head -1) Started!" + echolog "Global_Socks5:$($(first_type xray) version | head -1) Started!" ;; trojan) #client gen_config_file $LOCAL_SERVER $type 4 $local_port @@ -1055,42 +1407,50 @@ start_local() { ln_start_bin $(first_type naive) naive --config $local_config_file echolog "Global_Socks5:$($(first_type naive) --version | head -1) Started!" ;; - hysteria2) - if [ "$_local" == "2" ]; then - gen_config_file $LOCAL_SERVER $type 4 0 $local_port - ln_start_bin $(first_type hysteria) hysteria client --config $local_config_file - echolog "Global_Socks5:$($(first_type hysteria) version | grep Version | awk '{print "Hysteria2: " $2}') Started!" - fi - ;; tuic) - if [ "$_local" == "2" ]; then - gen_config_file $LOCAL_SERVER $type 4 $local_port - ln_start_bin $(first_type tuic-client) tuic-client --config $local_config_file - echolog "Global Socks5:$($(first_type tuic-client) --version) Started!" - fi + local mihomo_bin="$(first_type mihomo)" + local instance_key="${LOCAL_SERVER}-local" + [ -n "$probe_instance_key" ] && instance_key="$probe_instance_key" + [ -x "$mihomo_bin" ] || { + echolog "Global_Socks5:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。" + return 1 + } + prepare_tuic_runtime_config "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" || return 1 + local tuic_workdir="$(get_tuic_workdir "$instance_key")" + ln_start_bin "$mihomo_bin" tuic-local -d "$tuic_workdir" -f "$tuic_workdir/config.yaml" + echolog "Global_Socks5:Mihomo (TUIC) Started!" ;; - shadowtls) - #respective config for global socks and main node - if [ "$_local" == "2" ]; then - gen_config_file $LOCAL_SERVER $type 4 "10${tmp_tcp_local_port}" + shadowtls) + #respective config for global socks and main node + if [ "$_local" == "2" ]; then + gen_config_file $LOCAL_SERVER $type 4 "10${tmp_tcp_local_port}" gen_config_file $LOCAL_SERVER $type 4 0 $local_port chain/"10${tmp_tcp_local_port}" ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_local_config_file local chain_type=$(uci_get_by_name $LOCAL_SERVER chain_type) case ${chain_type} in vmess) - ln_start_bin $(first_type xray v2ray) v2ray run -c $local_config_file + ln_start_bin $(first_type xray) v2ray run -c $local_config_file echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type xray) --version) Started!" ;; sslocal) ln_start_bin $(first_type sslocal) sslocal -c $local_config_file echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type sslocal) --version) Started!" ;; - esac - fi - ;; - *) - [ -e /proc/sys/net/ipv6 ] && local listenip='-i ::' - ln_start_bin $(first_type microsocks) microsocks $listenip -p $local_port tcp-udp-ssr-local + esac + fi + ;; + clash) + echolog "Global_Socks5:Clash total node is only supported when using Same as Global Server." + return 1 + ;; + socks5) + echolog "Global_Socks5:Socks5 transparent-only node cannot be used as generic Socks outbound." + return 1 + ;; + *) + local listenip='-i 0.0.0.0' + [ -e /proc/sys/net/ipv6 ] && listenip='-i ::' + ln_start_bin $(first_type microsocks) microsocks $listenip -p $local_port tcp-udp-ssr-local echolog "Global_Socks5:$type Started!" ;; esac @@ -1098,13 +1458,192 @@ start_local() { return 0 } -Start_Run() { - if [ "$(uci_get_by_type global threads 0)" == "0" ]; then - local threads=$(cat /proc/cpuinfo | grep 'processor' | wc -l) +start_local_with_port() { + start_local "$1" +} + +gen_3proxy_config() { + local cfg_file="$1" + local listen_port="$2" + local relay_socks_port="$3" + local auth_mode="$4" + local auth_user="$5" + local auth_pass="$6" + + cat <<-EOF >"$cfg_file" + daemon + pidfile $TMP_PATH/3proxy.pid + nserver 8.8.8.8 + nscache 65536 + timeouts 1 5 30 60 180 1800 15 60 + authcache ip 60 + internal 0.0.0.0 + external 0.0.0.0 + flush + EOF + + if [ "$auth_mode" = "password" ] && [ -n "$auth_user" ]; then + cat <<-EOF >>"$cfg_file" + auth strong + users $auth_user:CL:$auth_pass + allow $auth_user + EOF else - local threads=$(uci_get_by_type global threads) + cat <<-'EOF' >>"$cfg_file" + auth none + allow * + EOF fi - if [ "$(uci_get_by_name $GLOBAL_SERVER kcp_enable 0)" == "1" ]; then + + cat <<-EOF >>"$cfg_file" + parent 1000 socks5 127.0.0.1 $relay_socks_port + proxy -n -a -p$listen_port + flush + EOF +} + +start_http_proxy() { + [ "$HTTP_SERVER" = "nil" ] && return 1 + + local http_local_port=$(uci_get_by_type http_proxy local_port 3128) + local http_auth_mode=$(uci_get_by_type http_proxy http_auth none) + local http_user=$(uci_get_by_type http_proxy http_user) + local http_pass=$(uci_get_by_type http_proxy http_pass) + local http_server_type=$(uci_get_by_name $HTTP_SERVER type) + local need_local_start=1 + local prev_local_server="$LOCAL_SERVER" + local prev_local_config_file="$local_config_file" + local prev_local_flag="$local_enable" + local prev_local_mode="$_local" + local http_config_file="$TMP_PATH/3proxy-ssr-http.cfg" + local global_socks_enabled=$(uci_get_by_type socks5_proxy enabled 0) + local global_socks_port=$(uci_get_by_type socks5_proxy local_port 1080) + local global_socks_server=$(uci_get_by_type socks5_proxy server nil) + local resolved_socks_server="$global_socks_server" + local http_socks_port="$global_socks_port" + + if [ -z "$http_socks_port" ] || [ "$http_socks_port" = "0" ]; then + http_socks_port=1080 + fi + + [ "$http_local_port" = "$http_socks_port" ] && { + echolog "Global_HTTP: HTTP 监听端口与上游 SOCKS5 端口不能相同。" + return 1 + } + + if [ "$resolved_socks_server" = "same" ]; then + resolved_socks_server="$GLOBAL_SERVER" + fi + + if [ "$global_socks_enabled" = "1" ] && [ "$http_socks_port" = "$global_socks_port" ] && [ "$resolved_socks_server" != "$HTTP_SERVER" ]; then + echolog "Global_HTTP: Relay SOCKS5 Port 已被全局 SOCKS5 代理占用,请改用其他端口。" + return 1 + fi + + if [ "$http_auth_mode" = "password" ] && [ -z "$http_user" ]; then + echolog "Global_HTTP: HTTP 认证模式为 password 时必须填写用户名。" + return 1 + fi + + case "$http_server_type" in + socks5) + echolog "Global_HTTP: 当前节点类型不支持作为 3proxy 的上游 SOCKS5 出口。" + return 1 + ;; + esac + + if [ "$http_server_type" = "clash" ]; then + if [ "$HTTP_SERVER" != "$GLOBAL_SERVER" ]; then + echolog "Global_HTTP: Clash 节点仅支持选择 Same as Global Server 进行复用。" + return 1 + fi + if [ "$http_socks_port" != "$global_socks_port" ] && [ "$global_socks_enabled" = "1" ] && [ "$resolved_socks_server" = "$HTTP_SERVER" ]; then + echolog "Global_HTTP: Mihomo/Clash 仅支持单个 socks-port,请让 HTTP 代理与全局 SOCKS5 使用相同的上游 SOCKS5 端口。" + return 1 + fi + fi + + if supports_builtin_http "$http_server_type" && [ "$HTTP_SERVER" = "$GLOBAL_SERVER" ]; then + need_local_start=0 + elif [ "$global_socks_enabled" = "1" ] && [ "$resolved_socks_server" = "$HTTP_SERVER" ] && [ "$global_socks_port" = "$http_socks_port" ]; then + need_local_start=0 + else + LOCAL_SERVER="$HTTP_SERVER" + local_config_file="$TMP_PATH/tcp-http-ssr-local.json" + _local="2" + local_enable=0 + start_local_with_port "$http_socks_port" || { + LOCAL_SERVER="$prev_local_server" + local_config_file="$prev_local_config_file" + local_enable="$prev_local_flag" + _local="$prev_local_mode" + return 1 + } + LOCAL_SERVER="$prev_local_server" + local_config_file="$prev_local_config_file" + local_enable="$prev_local_flag" + _local="$prev_local_mode" + fi + + [ "$need_local_start" = "0" ] && echolog "Global_HTTP: Reuse existing local Socks5 upstream on port $http_socks_port." + + gen_3proxy_config "$http_config_file" "$http_local_port" "$http_socks_port" "$http_auth_mode" "$http_user" "$http_pass" + ln_start_bin $(first_type 3proxy) 3proxy "$http_config_file" + echolog "Global_HTTP:3proxy HTTP/HTTPS Proxy Started!" + http_enable=1 + return 0 +} + +get_udp_relay_mode() { + local node="$1" + local type + local proto + local socks_ver + + type=$(uci_get_by_name "$node" type) + [ "$type" = "ss-rust" ] && type="ss" + + case "$type" in + ss|ssr|hysteria2) + echo "native" + ;; + clash|tuic) + echo "native" + ;; + trojan|shadowtls) + echo "split" + ;; + socks5) + echo "split" + ;; + naiveproxy) + echo "disabled" + ;; + v2ray) + proto=$(uci_get_by_name "$node" v2ray_protocol) + case "$proto" in + http) + echo "disabled" + ;; + socks) + socks_ver=$(uci_get_by_name "$node" socks_ver 5) + [ "$socks_ver" = "5" ] && echo "native" || echo "disabled" + ;; + *) + echo "native" + ;; + esac + ;; + *) + echo "disabled" + ;; + esac +} + +Start_Run() { + local threads=$(get_configured_threads) + local global_server_type=$(uci_get_by_name $GLOBAL_SERVER type) + if [ "$(uci_get_by_name $GLOBAL_SERVER kcp_enable 0)" == "1" ] && [ "$global_server_type" != "ss" ]; then [ ! -f "/usr/bin/kcptun-client" ] && return 1 local kcp_str=$(/usr/bin/kcptun-client -v | grep kcptun | wc -l) [ "0" == "$kcp_str" ] && return 1 @@ -1132,36 +1671,59 @@ Start_Run() { # fi #} fi - local tcp_port=$(uci_get_by_name $GLOBAL_SERVER local_port) - local global_server_type=$(uci_get_by_name $GLOBAL_SERVER type) + local tcp_port=$(get_default_node_local_port) local type=$global_server_type - if [ "$global_server_type" = "ss-rust" ] || [ "$global_server_type" = "ss-libev" ]; then + if [ "$global_server_type" = "ss-rust" ]; then type="ss" fi case "$type" in ss | ssr) - gen_config_file $GLOBAL_SERVER $type 1 $tcp_port - if [ "$global_server_type" = "ss-libev" ] || [ "$global_server_type" = "ssr" ]; then + if [ "$global_server_type" = "ssr" ]; then + gen_config_file $GLOBAL_SERVER $type 1 $tcp_port ss_program="$(first_type ${type}-redir)" - elif [ "$global_server_type" = "ss-rust" ]; then - ss_program="$(first_type ${type}local)" - fi - echolog "$(get_name $type) program is: $ss_program" - # 获取当前软链接指向的执行文件路径 - old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null) - # **当新旧执行文件路径不同时,删除旧链接** - if [ "$old_ss_program" != "$ss_program" ]; then - rm -rf "$TMP_PATH/bin/${type}-redir" - fi - for i in $(seq 1 $threads); do + echolog "$(get_name $type) program is: $ss_program" + old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null) + if [ "$old_ss_program" != "$ss_program" ]; then + rm -rf "$TMP_PATH/bin/${type}-redir" + fi + for i in $(seq 1 $threads); do + ln_start_bin $ss_program ${type}-redir -c $tcp_config_file + done + echolog "Main node:$(get_name $type) $threads Threads Started!" + elif [ "$global_server_type" = "ss" ] || { [ "$global_server_type" = "ss-rust" ] && use_mihomo_for_ss_rust_client; }; then + local mihomo_bin="$(first_type mihomo)" + local ss_socks_port=0 + [ -x "$mihomo_bin" ] || { + echolog "Main node:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。" + return 1 + } + if [ "$_local" = "1" ] && [ "$LOCAL_SERVER" = "$GLOBAL_SERVER" ]; then + ss_socks_port=$(uci_get_by_type socks5_proxy local_port 1080) + fi + prepare_ss_mihomo_runtime_config "$GLOBAL_SERVER" "$tcp_port" "$ss_socks_port" "$GLOBAL_SERVER" "redir" || return 1 + local ss_workdir="$(get_ss_mihomo_workdir "$GLOBAL_SERVER")" + ln_start_bin "$mihomo_bin" ssr-retcp -d "$ss_workdir" -f "$ss_workdir/config.yaml" + if [ "$global_server_type" = "ss-rust" ]; then + echolog "Main node:ss-rust 主程序不存在,已回退到 Mihomo (Shadowsocks) 启动。" + else + echolog "Main node:Mihomo (Shadowsocks) Started!" + fi + else + gen_config_file $GLOBAL_SERVER $type 1 $tcp_port + ss_program="$(first_type sslocal)" + echolog "ShadowSocks program is: $ss_program" + old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null) + if [ "$old_ss_program" != "$ss_program" ]; then + rm -rf "$TMP_PATH/bin/${type}-redir" + fi ln_start_bin $ss_program ${type}-redir -c $tcp_config_file - done - echolog "Main node:$(get_name $type) $threads Threads Started!" + echolog "Main node:Shadowsocks-rust Started!" + fi ;; v2ray) gen_config_file $GLOBAL_SERVER $type 1 $tcp_port $socks_port - ln_start_bin $(first_type xray v2ray) v2ray run -c $tcp_config_file - echolog "Main node:$($(first_type xray v2ray) version | head -1) Started!" + ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file + echolog "Main node:$($(first_type xray) version | head -1) Started!" ;; trojan) gen_config_file $GLOBAL_SERVER $type 1 $tcp_port @@ -1175,27 +1737,24 @@ Start_Run() { ln_start_bin $(first_type naive) naive $tcp_config_file echolog "Main node:$($(first_type naive) --version 2>&1 | head -1) , $threads Threads Started!" ;; - hysteria2) - gen_config_file $GLOBAL_SERVER $type 1 $tcp_port $socks_port - ln_start_bin $(first_type hysteria) hysteria client --config $tcp_config_file - echolog "Main node:$($(first_type hysteria) version | grep Version | awk '{print "Hysteria2: " $2}') Started!" - ;; tuic) - local PARAM - [ $mode == "tcp" ] && PARAM="-T" || PARAM="" - gen_config_file $GLOBAL_SERVER $type 1 $tmp_tcp_local_port - ln_start_bin $(first_type tuic-client) tuic-client --config $tcp_config_file - ln_start_bin $(first_type ipt2socks) ipt2socks "$PARAM" -R -b 0.0.0.0 -4 -s 127.0.0.1 -p $tmp_tcp_local_port -l $tcp_port - if [ -n $socks_port ] && [ $GLOBAL_SERVER == $LOCAL_SERVER ]; then #start a new tuic instance - gen_config_file $GLOBAL_SERVER $type 4 $socks_port - ln_start_bin $(first_type tuic-client) tuic-client --config $local_config_file - echolog "Global Socks5:$($(first_type tuic-client) --version) Started!" + local mihomo_bin="$(first_type mihomo)" + local tuic_socks_port=0 + [ -x "$mihomo_bin" ] || { + echolog "Main node:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。" + return 1 + } + if [ "$_local" = "1" ] && [ "$LOCAL_SERVER" = "$GLOBAL_SERVER" ]; then + tuic_socks_port=$(uci_get_by_type socks5_proxy local_port 1080) fi - echolog "Main node:$($(first_type tuic-client) --version) Started!" + prepare_tuic_runtime_config "$GLOBAL_SERVER" "$tcp_port" "$tuic_socks_port" "$GLOBAL_SERVER" "redir" || return 1 + local tuic_workdir="$(get_tuic_workdir "$GLOBAL_SERVER")" + ln_start_bin "$mihomo_bin" ssr-retcp -d "$tuic_workdir" -f "$tuic_workdir/config.yaml" + echolog "Main node:Mihomo (TUIC) Started!" ;; - shadowtls) - if [ -z "$socks_port" ]; then - gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" + shadowtls) + if [ -z "$socks_port" ]; then + gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" 0 chain else gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" @@ -1205,7 +1764,7 @@ Start_Run() { case ${chain_type} in vmess) ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file - ln_start_bin $(first_type xray v2ray) v2ray run -c $tcp_config_file + ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file echolog "Mian node:shadow-tls chain-to $($(first_type xray) --version) Started!" ;; sslocal) @@ -1213,25 +1772,49 @@ Start_Run() { ln_start_bin $(first_type sslocal) sslocal -c $tcp_config_file echolog "Main node:shadow-tls chain-to $($(first_type sslocal) --version) Started!" ;; - esac - ;; + esac + ;; + clash) + local clash_socks_port=0 + local mihomo_bin="$(first_type mihomo)" + clash_socks_port="$(resolve_global_clash_socks_port)" + if [ -z "$clash_socks_port" ] && [ "$_local" == "1" ] && [ "$LOCAL_SERVER" = "$GLOBAL_SERVER" ]; then + clash_socks_port=$(uci_get_by_type socks5_proxy local_port 1080) + fi + [ -x "$mihomo_bin" ] || { + echolog "Main node:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。" + return 1 + } + download_clash_config "$GLOBAL_SERVER" || { + echolog "Main node:Clash 配置不可用,启动中止。" + return 1 + } + prepare_clash_runtime_config "$GLOBAL_SERVER" "$tcp_port" "$clash_socks_port" || { + echolog "Main node:Clash 运行配置生成失败,启动中止。" + return 1 + } + local clash_workdir="$(get_clash_workdir "$GLOBAL_SERVER")" + ln_start_bin "$mihomo_bin" ssr-retcp -d "$clash_workdir" -f "$clash_workdir/config.yaml" + echolog "Main node:Mihomo (Clash) Started!" + ;; socks5) - if [ "$(uci_get_by_name $GLOBAL_SERVER auth_enable 0)" == "1" ]; then - local auth="-a $(uci_get_by_name $GLOBAL_SERVER username) -k $(uci_get_by_name $GLOBAL_SERVER password)" + local ipt2socks_bin="$(first_type ipt2socks)" + local auth_opts="" + [ -x "$ipt2socks_bin" ] || { + echolog "Main node:Socks5 缺少 ipt2socks,无法启动透明代理。" + return 1 + } + if [ "$(uci_get_by_name $GLOBAL_SERVER auth_enable 0)" = "1" ]; then + auth_opts="-a $(uci_get_by_name $GLOBAL_SERVER username) -k $(uci_get_by_name $GLOBAL_SERVER password)" fi - ln_start_bin $(first_type ipt2socks) ipt2socks $tcp_config_file -R -4 -j $threads -s $(uci_get_by_name $GLOBAL_SERVER server) -p $(uci_get_by_name $GLOBAL_SERVER server_port) -l $tcp_port $auth - #gen_config_file $GLOBAL_SERVER $type 1 $tcp_port - #for i in $(seq 1 $threads); do - # ln_start_bin $(first_type redsocks2) redsocks2 -c $tcp_config_file - #done - echolog "Main node:Socks5 REDIRECT/TPROXY $threads Threads Started!" - ;; - tun) - gen_config_file $GLOBAL_SERVER $type 1 $tcp_port for i in $(seq 1 $threads); do - ln_start_bin $(first_type redsocks2) redsocks2 -c $tcp_config_file + ln_start_bin "$ipt2socks_bin" ipt2socks \ + -T -R -r -b 0.0.0.0 -4 \ + -s "$(uci_get_by_name $GLOBAL_SERVER server)" \ + -p "$(uci_get_by_name $GLOBAL_SERVER server_port)" \ + -l "$tcp_port" $auth_opts done - echolog "Main node:Network Tunnel REDIRECT $threads Threads Started!" + echolog "Main node:Socks5 via IPT2Socks $threads Threads Started!" ;; esac redir_tcp=1 @@ -1244,6 +1827,17 @@ load_config() { else GLOBAL_SERVER=$switch_server fi + if [ "$(uci_get_by_type socks5_proxy enabled 0)" == "1" ]; then + uci -q set "$NAME.@socks5_proxy[0].server=same" + else + uci -q set "$NAME.@socks5_proxy[0].server=nil" + fi + if [ "$(uci_get_by_type http_proxy enabled 0)" == "1" ]; then + uci -q set "$NAME.@http_proxy[0].server=same" + else + uci -q set "$NAME.@http_proxy[0].server=nil" + fi + uci -q commit "$NAME" if [ "$(uci_get_by_type socks5_proxy enabled 0)" == "1" ]; then # 只有开启 全局socks 才需要取值 LOCAL_SERVER=$(uci_get_by_type socks5_proxy server nil) @@ -1251,88 +1845,65 @@ load_config() { # 没有开启 设置为 nil LOCAL_SERVER=nil fi + if [ "$(uci_get_by_type http_proxy enabled 0)" == "1" ]; then + HTTP_SERVER=$(uci_get_by_type http_proxy server nil) + else + HTTP_SERVER=nil + fi if [ "$GLOBAL_SERVER" == "nil" ]; then + [ "$HTTP_SERVER" = "same" ] && HTTP_SERVER=nil mode="tcp,udp" _local="2" local_config_file=$TMP_PATH/tcp-udp-ssr-local.json start_local return 1 fi - UDP_RELAY_SERVER=$(uci_get_by_type global udp_relay_server nil) - if [ "$(uci_get_by_type global netflix_enable 0)" == "1" ]; then - # 只有开启 NetFlix分流 才需要取值 - SHUNT_SERVER=$(uci_get_by_type global netflix_server nil) - else - # 没有开启 设置为 nil - SHUNT_SERVER=nil - fi - #tcp_config_file=$TMP_PATH/tcp-udp-dual-ssr-retcp.json + UDP_RELAY_SERVER=$GLOBAL_SERVER + local udp_mode=$(get_udp_relay_mode "$GLOBAL_SERVER") tcp_config_file=$TMP_PATH/tcp-only-ssr-retcp.json - case "$UDP_RELAY_SERVER" in - nil) - #mode="tcp,udp" + case "$udp_mode" in + disabled) + echolog "提示:当前节点类型不提供 UDP 透明代理,也不会启用任何 UDP 规则,仅启用 TCP 透明代理。" mode="tcp" ARG_UDP="" + ARG_UDP_RULES="-y" udp_config_file="" ;; - $GLOBAL_SERVER | same) + native) mode="tcp,udp" tcp_config_file=$TMP_PATH/tcp-udp-ssr-retcp.json ARG_UDP="-u" - UDP_RELAY_SERVER=$GLOBAL_SERVER + ARG_UDP_RULES="" ;; - *) + split) mode="udp" udp_config_file=$TMP_PATH/udp-only-ssr-reudp.json ARG_UDP="-U" + ARG_UDP_RULES="" start_udp - #mode="tcp,udp" mode="tcp" ;; esac case "$LOCAL_SERVER" in nil) - _local="0" - ;; - $GLOBAL_SERVER | same) - _local="1" - LOCAL_SERVER=$GLOBAL_SERVER - local_config_file=$TMP_PATH/tcp-udp-ssr-local.json - start_local - local_enable=0 - ;; - $SHUNT_SERVER) - _local="3" - local_config_file=$TMP_PATH/tcp-udp-ssr-local.json - start_local - ;; + _local="0" + ;; + "$GLOBAL_SERVER"|same) + _local="1" + LOCAL_SERVER=$GLOBAL_SERVER + local_config_file=$TMP_PATH/tcp-udp-ssr-local.json + if ! supports_builtin_socks "$(uci_get_by_name $GLOBAL_SERVER type)"; then + start_local + fi + local_enable=0 + ;; *) _local="2" local_config_file=$TMP_PATH/tcp-udp-ssr-local.json start_local ;; esac - case "$SHUNT_SERVER" in - nil) - shunt="0" - ;; - $GLOBAL_SERVER | same) - shunt="1" - SHUNT_SERVER=$GLOBAL_SERVER - ;; - $LOCAL_SERVER) - shunt="$tmp_shunt_port" - shunt_config_file=$TMP_PATH/tcp-udp-ssr-local.json - shunt_dns_config_file=$TMP_PATH/shunt-dns-ssr-plus.json - start_shunt - ;; - *) - shunt="$tmp_shunt_port" - shunt_config_file=$TMP_PATH/shunt-ssr-retcp.json - shunt_dns_config_file=$TMP_PATH/shunt-dns-ssr-plus.json - start_shunt - ;; - esac + [ "$HTTP_SERVER" = "same" ] && HTTP_SERVER=$GLOBAL_SERVER return 0 } @@ -1379,39 +1950,108 @@ start_server() { fi fi fi - local node_type=$(uci_get_by_name $1 type) - local type=$node_type - if [ "$node_type" = "ss-rust" ] || [ "$node_type" = "ss-libev" ]; then - type="ss" - fi - case "$type" in - ss | ssr) - gen_service_file ${type} $1 $TMP_PATH/ssr-server$server_count.json - if [ "$node_type" = "ss-libev" ] || [ "$node_type" = "ssr" ]; then - ss_program="$(first_type ${type}-server)" - elif [ "$node_type" = "ss-rust" ]; then - ss_program="$(first_type ${type}server)" + local node_type=$(uci_get_by_name $1 type) + local type=$node_type + if [ "$node_type" = "ss-rust" ]; then + type="ss" fi - # 获取当前软链接指向的执行文件路径 - old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-server" 2>/dev/null) - # **当新旧执行文件路径不同时,删除旧链接** - if [ "$old_ss_program" != "$ss_program" ]; then - rm -rf "$TMP_PATH/bin/${type}-server" - fi - ln_start_bin $ss_program ${type}-server -c $TMP_PATH/ssr-server$server_count.json - echolog "Server: $(get_name ${type}) Server$server_count Started!" + case "$type" in + ss | ssr) + if [ "$node_type" = "ss" ] || { [ "$node_type" = "ss-rust" ] && use_mihomo_for_ss_rust_server; }; then + local mihomo_bin="$(first_type mihomo)" + [ -x "$mihomo_bin" ] || { + echolog "Server:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。" + return 1 + } + prepare_ss_server_mihomo_runtime_config "$1" || return 1 + local ss_server_workdir="$(get_ss_server_mihomo_workdir "$1")" + ln_start_bin "$mihomo_bin" ss-server-$server_count -d "$ss_server_workdir" -f "$ss_server_workdir/config.yaml" + if [ "$node_type" = "ss-rust" ]; then + echolog "Server:ss-rust 主程序不存在,已回退到 Mihomo Shadowsocks Server$server_count 启动。" + else + echolog "Server:Mihomo Shadowsocks Server$server_count Started!" + fi + else + gen_service_file ${type} $1 $TMP_PATH/ssr-server$server_count.json + if [ "$node_type" = "ssr" ]; then + ss_program="$(first_type ${type}-server)" + elif [ "$node_type" = "ss-rust" ] || [ "$node_type" = "ss" ]; then + ss_program="$(first_type ${type}server)" + fi + old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-server" 2>/dev/null) + if [ "$old_ss_program" != "$ss_program" ]; then + rm -rf "$TMP_PATH/bin/${type}-server" + fi + ln_start_bin $ss_program ${type}-server -c $TMP_PATH/ssr-server$server_count.json + echolog "Server: $(get_name ${type}) Server$server_count Started!" + fi ;; socks5) - [ -e /proc/sys/net/ipv6 ] && local listenip='-i ::' + local listenip='-i 0.0.0.0' + [ -e /proc/sys/net/ipv6 ] && listenip='-i ::' local username=$(uci_get_by_name $1 username) local password=$(uci_get_by_name $1 password) local auth_opts="" - if [ -n "$username" ] && [ -n "$password" ]; then - auth_opts="-u $username -P $password" + if [ -n "$username" ]; then + auth_opts="-u $username" + [ -n "$password" ] && auth_opts="$auth_opts -P $password" fi ln_start_bin $(first_type microsocks) microsocks $listenip -p $(uci_get_by_name $1 server_port) -1 $auth_opts ssr-server$server_count echolog "Server:Socks5 Server$server_count Started!" ;; + vmess|vless|trojan|shadowsocks) + # Xray 服务端支持 + local port=$(uci_get_by_name "$1" server_port) + local uuid=$(uci_get_by_name "$1" uuid) + local trojan_pass=$(uci_get_by_name "$1" trojan_password) + local ss_pass=$(uci_get_by_name "$1" ss_password) + local security=$(uci_get_by_name "$1" security "auto") + local network=$(uci_get_by_name "$1" network "tcp") + local ws_path=$(uci_get_by_name "$1" ws_path "/") + local ws_host=$(uci_get_by_name "$1" ws_host "") + local grpc_service=$(uci_get_by_name "$1" grpc_service "") + local alter_id=$(uci_get_by_name "$1" alter_id "0") + local tcp_guise=$(uci_get_by_name "$1" tcp_guise "none") + local tcp_guise_http_host=$(uci_get_by_name "$1" tcp_guise_http_host "") + local tcp_guise_http_path=$(uci_get_by_name "$1" tcp_guise_http_path "") + + # 确定密码/UUID + local password="" + local method="" + if [ "$type" = "vmess" ] || [ "$type" = "vless" ]; then + password="$uuid" + elif [ "$type" = "trojan" ]; then + password="$trojan_pass" + elif [ "$type" = "shadowsocks" ]; then + password="$ss_pass" + method="$security" + [ -z "$method" ] && method="chacha20-ietf-poly1305" + fi + + [ -z "$password" ] && { + echolog "Server: $type 服务端缺少密码/UUID,跳过启动" + return 1 + } + + local config_file="$TMP_PATH/xray-server-$server_count.json" + + # 生成 Xray 配置 + generate_xray_config "$type" "$port" "$password" "$security" "$method" "$network" "$ws_path" "$ws_host" "$grpc_service" "$alter_id" "$tcp_guise" "$tcp_guise_http_host" "$tcp_guise_http_path" "$config_file" + + if [ $? -ne 0 ]; then + echolog "Server: Xray $type 配置生成失败" + return 1 + fi + + local xray_bin=$(first_type xray) + if [ -z "$xray_bin" ]; then + echolog "Server: xray-core 未安装,无法启动 $type 服务端" + return 1 + fi + + ln_start_bin "$xray_bin" xray-server-$server_count run -c "$config_file" + echolog "Server: Xray $type 服务端 Server$server_count 已启动 (端口: $port)" + ;; esac server_port=$(uci_get_by_name $1 server_port) if [ "$USE_TABLES" = "nftables" ]; then @@ -1471,6 +2111,10 @@ start_server() { } start_switch() { + if [ "$(uci_get_by_name $GLOBAL_SERVER type)" = "clash" ] || [ "$(uci_get_by_name $GLOBAL_SERVER type)" = "tuic" ]; then + echolog "提示:Mihomo 托管节点不启用 SSR Plus 自动切换逻辑。" + return 0 + fi if [ "$(uci_get_by_type global enable_switch 0)" == "1" ]; then if [ -z "$switch_server" ]; then local switch_time=$(uci_get_by_type global switch_time)s @@ -1495,7 +2139,7 @@ start_xhttp_addr() { # 收集所有节点的 download_address 值,去掉空行并去重排序 { - for sec in "$GLOBAL_SERVER" "$SHUNT_SERVER" "$UDP_RELAY_SERVER"; do + for sec in "$GLOBAL_SERVER" "$UDP_RELAY_SERVER"; do local addr addr=$(uci_get_by_name "$sec" download_address) [ -n "$addr" ] && echo "$addr" @@ -1529,26 +2173,22 @@ start_xhttp_addr() { start_rules() { local server=$(get_host_ip $GLOBAL_SERVER) - local local_port=$(uci_get_by_name $GLOBAL_SERVER local_port) + local local_port=$(get_default_node_local_port) local lan_ac_ips=$(uci_get_by_type access_control lan_ac_ips) local lan_ac_mode=$(uci_get_by_type access_control lan_ac_mode) if [ "$kcp_enable_flag" == "0" -a "$redir_udp" == "1" ]; then local udp_server=$(get_host_ip $UDP_RELAY_SERVER) local udp_local_port=$tmp_udp_port fi - if [ "$shunt" != "0" ]; then - local shunt_ip=$(get_host_ip $SHUNT_SERVER) - fi if [ -n "$lan_ac_ips" ]; then case "$lan_ac_mode" in w | W | b | B) local ac_ips="$lan_ac_mode$lan_ac_ips" ;; esac fi gfwmode() { - case "$(uci_get_by_type global run_mode)" in + case "$(normalize_run_mode)" in gfw) echo "-g" ;; router) echo "-r" ;; - oversea) echo "-c" ;; all) echo "-z" ;; esac } @@ -1579,45 +2219,44 @@ start_rules() { fi elif [ "$USE_TABLES" = "iptables" ]; then ARG_A="" - fi - /usr/share/shadowsocksr/gfw2ipset.sh - /usr/bin/ssr-rules \ - -s "$server" \ - -l "$local_port" \ - -S "$udp_server" \ - -L "$udp_local_port" \ - -a "$ac_ips" \ - -i "/etc/ssrplus/china_ssr.txt" \ - -b "$(uci_get_by_type access_control wan_bp_ips)" \ - -w "$(uci_get_by_type access_control wan_fw_ips)" \ - -B "$(uci_get_by_type access_control lan_bp_ips)" \ - -p "$(uci_get_by_type access_control lan_fp_ips)" \ - -G "$(uci_get_by_type access_control lan_gm_ips)" \ - -m "$(uci_get_by_type access_control Interface)" \ - -D "$proxyport" \ - -F "$shunt" \ - -N "$shunt_ip" \ - -M "$(uci_get_by_type global netflix_proxy 0)" \ - -I "/etc/ssrplus/netflixip.list" \ - $(get_arg_out) $(gfwmode) $ARG_UDP $ARG_A + fi + /usr/share/shadowsocksr/gfw2ipset.sh + /usr/bin/ssr-rules \ + -s "$server" \ + -l "$local_port" \ + -S "$udp_server" \ + -L "$udp_local_port" \ + -a "$ac_ips" \ + -i "/etc/ssrplus/china_ssr.txt" \ + -b "$(uci_get_by_type access_control wan_bp_ips)" \ + -w "$(uci_get_by_type access_control wan_fw_ips)" \ + -B "$(uci_get_by_type access_control lan_bp_ips)" \ + -p "$(uci_get_by_type access_control lan_fp_ips)" \ + -G "$(uci_get_by_type access_control lan_gm_ips)" \ + -m "$(uci_get_by_type access_control Interface)" \ + -D "$proxyport" \ + $(get_arg_out) $(gfwmode) $ARG_UDP $ARG_UDP_RULES $ARG_A - return $? -} + return $? + } start() { set_lock echolog "----------start------------" - mkdir -p /var/run /var/lock /var/log $DNSMASQ_CONF_DIR $TMP_BIN_PATH $TMP_DNSMASQ_PATH + mkdir -p /var/run /var/lock /var/log $DNSMASQ_CONF_DIR $TMP_BIN_PATH $TMP_DNSMASQ_PATH $CLASH_CONFIG_DIR echo "conf-dir=${TMP_DNSMASQ_PATH}" >"$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" check_run_environment + normalize_run_mode >/dev/null + normalize_xray_protocol_nodes if load_config; then Start_Run + start_http_proxy start_xhttp_addr start_rules start_dns # Restore ipsets after rules creation if [ "$HAS_IPSET" -eq 1 ]; then - for setname in gfwlist china blacklist whitelist netflix; do + for setname in gfwlist china blacklist whitelist; do [ "$setname" = "gfwlist" ] && [ "$run_mode" != "gfw" ] && continue if [ -f "/tmp/ssrplus_save/${setname}.save" ]; then ipset restore -! < "/tmp/ssrplus_save/${setname}.save" 2>/dev/null @@ -1632,16 +2271,41 @@ start() { echolog "禁止连接的域名加载完毕。" if [ "$(uci_get_by_type global adblock 0)" == "1" ]; then echolog "未启动主节点,广告过滤正在加载。" - cp -f /etc/ssrplus/ad.conf $TMP_DNSMASQ_PATH/ + cp -f /etc/ssrplus/ad.conf "$TMP_DNSMASQ_PATH/" if [ -f "$TMP_DNSMASQ_PATH/ad.conf" ]; then # Optimize: Batch filter using grep instead of looping sed for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/deny.list; do if [ -s "$list_file" ]; then # Clean list file (remove comments and empty lines) - grep -vE '^\s*#|^\s*$' "$list_file" > "${list_file}.clean" + grep -vE '^\s*#|^\s*$' "$list_file" | sed 's/\r//g' > "${list_file}.clean" if [ -s "${list_file}.clean" ]; then - grep -v -F -f "${list_file}.clean" "$TMP_DNSMASQ_PATH/ad.conf" > "$TMP_DNSMASQ_PATH/ad.conf.tmp" - mv "$TMP_DNSMASQ_PATH/ad.conf.tmp" "$TMP_DNSMASQ_PATH/ad.conf" + tmp_file="$TMP_DNSMASQ_PATH/ad.conf.tmp" + awk -v list="${list_file}.clean" ' + BEGIN { + while ((getline line < list) > 0) { + gsub(/\r/, "", line) + if (line != "") { + domain[line] = 1 + # 支持泛域名 + domain["*." line] = 1 + } + } + close(list) + } + { + keep = 1 + # 匹配 server=/domain/ + if (match($0, /^server=\/([^\/]+)\//, m)) { + if (m[1] in domain) keep = 0 + } + # 匹配 ipset=/domain/ + if (match($0, /^ipset=\/([^\/]+)\//, m)) { + if (m[1] in domain) keep = 0 + } + if (keep) print + } + ' "$TMP_DNSMASQ_PATH/ad.conf" > "$tmp_file" + mv "$tmp_file" "$TMP_DNSMASQ_PATH/ad.conf" fi rm -f "${list_file}.clean" fi @@ -1649,6 +2313,7 @@ start() { fi echolog "广告过滤加载完毕。" fi + start_http_proxy fi /etc/init.d/dnsmasq restart >/dev/null 2>&1 check_server @@ -1683,7 +2348,7 @@ stop() { if [ "$run_mode" = "gfw" ]; then ipset save gfwlist > /tmp/ssrplus_save/gfwlist.save 2>/dev/null fi - for setname in china blacklist whitelist netflix; do + for setname in china blacklist whitelist; do ipset save $setname > /tmp/ssrplus_save/$setname.save 2>/dev/null done fi @@ -1737,21 +2402,21 @@ stop() { fi fi fi - if [ -z "$switch_server" ]; then - $PS -w | grep -v "grep" | grep ssr-switch | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & - rm -f /var/lock/ssr-switch.lock - killall -q -9 kcptun-client - fi - $PS -w | grep -v "grep" | grep ssr-monitor | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & - $PS -w | grep -v "grep" | grep ssr-rules | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & - $PS -w | grep -v "grep" | grep "sleep 0000" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & - ( \ - # Graceful kill first, so programs have the chance to stop its subprocesses - $PS -w | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill >/dev/null 2>&1 ; \ - sleep 3s; \ - # Force kill hanged programs - $PS -w | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 ; \ - ) + if [ -z "$switch_server" ]; then + ps_list | grep -v "grep" | grep ssr-switch | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & + rm -f /var/lock/ssr-switch.lock + killall -q -9 kcptun-client + fi + ps_list | grep -v "grep" | grep ssr-monitor | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & + ps_list | grep -v "grep" | grep ssr-rules | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & + ps_list | grep -v "grep" | grep "sleep 0000" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & + ( \ + # Graceful kill first, so programs have the chance to stop its subprocesses + ps_list | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill >/dev/null 2>&1 ; \ + sleep 3s; \ + # Force kill hanged programs + ps_list | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 ; \ + ) killall -q -9 v2ray-plugin obfs-local xray-plugin shadow-tls rm -f /var/lock/ssr-monitor.lock if [ "$(uci -q get "dhcp.@dnsmasq[0]._unused_ssrp_changed")" = "1" ]; then @@ -1762,12 +2427,14 @@ stop() { uci -q del "dhcp.@dnsmasq[0]._unused_ssrp_changed" uci -q commit "dhcp" fi - if [ -f "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" ]; then - rm -rf $DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf \ - $TMP_DNSMASQ_PATH \ - $TMP_PATH/*-ssr-*.json \ - $TMP_PATH/ssr-server*.json \ - $TMP_PATH/*-config-*.json + if [ -f "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" ]; then + rm -rf $DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf \ + $TMP_DNSMASQ_PATH \ + $TMP_PATH/*-ssr-*.json \ + $TMP_PATH/3proxy* \ + $TMP_PATH/clash-* \ + $TMP_PATH/ssr-server*.json \ + $TMP_PATH/*-config-*.json /etc/init.d/dnsmasq restart >/dev/null 2>&1 fi diff --git a/luci-app-ssr-plus/root/etc/ssrplus/netflix.list b/luci-app-ssr-plus/root/etc/ssrplus/netflix.list deleted file mode 100644 index f98f711b3..000000000 --- a/luci-app-ssr-plus/root/etc/ssrplus/netflix.list +++ /dev/null @@ -1,25 +0,0 @@ -amazonaws.com -aws.amazon.com -awsstatic.com -fast.com -netflix.com -netflix.net -nflxext.com -nflximg.net -nflxso.net -nflxvideo.net -netflixdnstest0.com -netflixdnstest1.com -netflixdnstest2.com -netflixdnstest3.com -netflixdnstest4.com -netflixdnstest5.com -netflixdnstest6.com -netflixdnstest7.com -netflixdnstest8.com -netflixdnstest9.com -hulu.com -huluim.com -hbonow.com -hbogo.com -hbo.com diff --git a/luci-app-ssr-plus/root/etc/ssrplus/oversea_list.conf b/luci-app-ssr-plus/root/etc/ssrplus/oversea_list.conf deleted file mode 100644 index ae50fb90d..000000000 --- a/luci-app-ssr-plus/root/etc/ssrplus/oversea_list.conf +++ /dev/null @@ -1,192 +0,0 @@ -server=/v.youku.com/127.0.0.1#5335 -server=/api.youku.com/127.0.0.1#5335 -server=/v2.tudou.com/127.0.0.1#5335 -server=/www.tudou.com/127.0.0.1#5335 -server=/s.plcloud.music.qq.com/127.0.0.1#5335 -server=/i.y.qq.com/127.0.0.1#5335 -server=/hot.vrs.sohu.com/127.0.0.1#5335 -server=/live.tv.sohu.com/127.0.0.1#5335 -server=/pad.tv.sohu.com/127.0.0.1#5335 -server=/my.tv.sohu.com/127.0.0.1#5335 -server=/hot.vrs.letv.com/127.0.0.1#5335 -server=/data.video.qiyi.com/127.0.0.1#5335 -server=/cache.video.qiyi.com/127.0.0.1#5335 -server=/cache.vip.qiyi.com/127.0.0.1#5335 -server=/vv.video.qq.com/127.0.0.1#5335 -server=/tt.video.qq.com/127.0.0.1#5335 -server=/ice.video.qq.com/127.0.0.1#5335 -server=/tjsa.video.qq.com/127.0.0.1#5335 -server=/a10.video.qq.com/127.0.0.1#5335 -server=/xyy.video.qq.com/127.0.0.1#5335 -server=/vcq.video.qq.com/127.0.0.1#5335 -server=/vsh.video.qq.com/127.0.0.1#5335 -server=/vbj.video.qq.com/127.0.0.1#5335 -server=/bobo.video.qq.com/127.0.0.1#5335 -server=/flvs.video.qq.com/127.0.0.1#5335 -server=/bkvv.video.qq.com/127.0.0.1#5335 -server=/info.zb.qq.com/127.0.0.1#5335 -server=/geo.js.kankan.xunlei.com/127.0.0.1#5335 -server=/web-play.pptv.com/127.0.0.1#5335 -server=/web-play.pplive.cn/127.0.0.1#5335 -server=/dyn.ugc.pps.tv/127.0.0.1#5335 -server=/v.pps.tv/127.0.0.1#5335 -server=/inner.kandian.com/127.0.0.1#5335 -server=/ipservice.163.com/127.0.0.1#5335 -server=/so.open.163.com/127.0.0.1#5335 -server=/zb.s.qq.com/127.0.0.1#5335 -server=/ip.kankan.xunlei.com/127.0.0.1#5335 -server=/vxml.56.com/127.0.0.1#5335 -server=/music.sina.com.cn/127.0.0.1#5335 -server=/play.baidu.com/127.0.0.1#5335 -server=/v.iask.com/127.0.0.1#5335 -server=/tv.weibo.com/127.0.0.1#5335 -server=/wtv.v.iask.com/127.0.0.1#5335 -server=/video.sina.com.cn/127.0.0.1#5335 -server=/www.yinyuetai.com/127.0.0.1#5335 -server=/api.letv.com/127.0.0.1#5335 -server=/live.gslb.letv.com/127.0.0.1#5335 -server=/static.itv.letv.com/127.0.0.1#5335 -server=/ip.apps.cntv.cn/127.0.0.1#5335 -server=/vdn.apps.cntv.cn/127.0.0.1#5335 -server=/vdn.live.cntv.cn/127.0.0.1#5335 -server=/vip.sports.cntv.cn/127.0.0.1#5335 -server=/a.play.api.3g.youku.com/127.0.0.1#5335 -server=/i.play.api.3g.youku.com/127.0.0.1#5335 -server=/api.3g.youku.com/127.0.0.1#5335 -server=/tv.api.3g.youku.com/127.0.0.1#5335 -server=/play.api.3g.youku.com/127.0.0.1#5335 -server=/play.api.3g.tudou.com/127.0.0.1#5335 -server=/tv.api.3g.tudou.com/127.0.0.1#5335 -server=/api.3g.tudou.com/127.0.0.1#5335 -server=/api.tv.sohu.com/127.0.0.1#5335 -server=/access.tv.sohu.com/127.0.0.1#5335 -server=/iface.iqiyi.com/127.0.0.1#5335 -server=/iface2.iqiyi.com/127.0.0.1#5335 -server=/cache.m.iqiyi.com/127.0.0.1#5335 -server=/dynamic.app.m.letv.com/127.0.0.1#5335 -server=/dynamic.meizi.app.m.letv.com/127.0.0.1#5335 -server=/dynamic.search.app.m.letv.com/127.0.0.1#5335 -server=/dynamic.live.app.m.letv.com/127.0.0.1#5335 -server=/listso.m.areainfo.ppstream.com/127.0.0.1#5335 -server=/epg.api.pptv.com/127.0.0.1#5335 -server=/play.api.pptv.com/127.0.0.1#5335 -server=/m.letv.com/127.0.0.1#5335 -server=/interface.bilibili.com/127.0.0.1#5335 -server=/3g.music.qq.com/127.0.0.1#5335 -server=/mqqplayer.3g.qq.com/127.0.0.1#5335 -server=/proxy.music.qq.com/127.0.0.1#5335 -server=/proxymc.qq.com/127.0.0.1#5335 -server=/ip2.kugou.com/127.0.0.1#5335 -server=/ip.kugou.com/127.0.0.1#5335 -server=/client.api.ttpod.com/127.0.0.1#5335 -server=/mobi.kuwo.cn/127.0.0.1#5335 -server=/mobilefeedback.kugou.com/127.0.0.1#5335 -server=/tingapi.ting.baidu.com/127.0.0.1#5335 -server=/music.baidu.com/127.0.0.1#5335 -server=/serviceinfo.sdk.duomi.com/127.0.0.1#5335 -server=/music.163.com/127.0.0.1#5335 -server=/www.xiami.com/127.0.0.1#5335 -server=/spark.api.xiami.com/127.0.0.1#5335 -server=/iplocation.geo.qiyi.com/127.0.0.1#5335 -server=/sns.video.qq.com/127.0.0.1#5335 -server=/v5.pc.duomi.com/127.0.0.1#5335 -server=/tms.is.ysten.com/127.0.0.1#5335 -server=/internal.check.duokanbox.com/127.0.0.1#5335 -server=/openapi.youku.com/127.0.0.1#5335 -server=/y.qq.com/127.0.0.1#5335 -ipset=/v.youku.com/oversea -ipset=/api.youku.com/oversea -ipset=/v2.tudou.com/oversea -ipset=/www.tudou.com/oversea -ipset=/s.plcloud.music.qq.com/oversea -ipset=/i.y.qq.com/oversea -ipset=/hot.vrs.sohu.com/oversea -ipset=/live.tv.sohu.com/oversea -ipset=/pad.tv.sohu.com/oversea -ipset=/my.tv.sohu.com/oversea -ipset=/hot.vrs.letv.com/oversea -ipset=/data.video.qiyi.com/oversea -ipset=/cache.video.qiyi.com/oversea -ipset=/cache.vip.qiyi.com/oversea -ipset=/vv.video.qq.com/oversea -ipset=/tt.video.qq.com/oversea -ipset=/ice.video.qq.com/oversea -ipset=/tjsa.video.qq.com/oversea -ipset=/a10.video.qq.com/oversea -ipset=/xyy.video.qq.com/oversea -ipset=/vcq.video.qq.com/oversea -ipset=/vsh.video.qq.com/oversea -ipset=/vbj.video.qq.com/oversea -ipset=/bobo.video.qq.com/oversea -ipset=/flvs.video.qq.com/oversea -ipset=/bkvv.video.qq.com/oversea -ipset=/info.zb.qq.com/oversea -ipset=/geo.js.kankan.xunlei.com/oversea -ipset=/web-play.pptv.com/oversea -ipset=/web-play.pplive.cn/oversea -ipset=/dyn.ugc.pps.tv/oversea -ipset=/v.pps.tv/oversea -ipset=/inner.kandian.com/oversea -ipset=/ipservice.163.com/oversea -ipset=/so.open.163.com/oversea -ipset=/zb.s.qq.com/oversea -ipset=/ip.kankan.xunlei.com/oversea -ipset=/vxml.56.com/oversea -ipset=/music.sina.com.cn/oversea -ipset=/play.baidu.com/oversea -ipset=/v.iask.com/oversea -ipset=/tv.weibo.com/oversea -ipset=/wtv.v.iask.com/oversea -ipset=/video.sina.com.cn/oversea -ipset=/www.yinyuetai.com/oversea -ipset=/api.letv.com/oversea -ipset=/live.gslb.letv.com/oversea -ipset=/static.itv.letv.com/oversea -ipset=/ip.apps.cntv.cn/oversea -ipset=/vdn.apps.cntv.cn/oversea -ipset=/vdn.live.cntv.cn/oversea -ipset=/vip.sports.cntv.cn/oversea -ipset=/a.play.api.3g.youku.com/oversea -ipset=/i.play.api.3g.youku.com/oversea -ipset=/api.3g.youku.com/oversea -ipset=/tv.api.3g.youku.com/oversea -ipset=/play.api.3g.youku.com/oversea -ipset=/play.api.3g.tudou.com/oversea -ipset=/tv.api.3g.tudou.com/oversea -ipset=/api.3g.tudou.com/oversea -ipset=/api.tv.sohu.com/oversea -ipset=/access.tv.sohu.com/oversea -ipset=/iface.iqiyi.com/oversea -ipset=/iface2.iqiyi.com/oversea -ipset=/cache.m.iqiyi.com/oversea -ipset=/dynamic.app.m.letv.com/oversea -ipset=/dynamic.meizi.app.m.letv.com/oversea -ipset=/dynamic.search.app.m.letv.com/oversea -ipset=/dynamic.live.app.m.letv.com/oversea -ipset=/listso.m.areainfo.ppstream.com/oversea -ipset=/epg.api.pptv.com/oversea -ipset=/play.api.pptv.com/oversea -ipset=/m.letv.com/oversea -ipset=/interface.bilibili.com/oversea -ipset=/3g.music.qq.com/oversea -ipset=/mqqplayer.3g.qq.com/oversea -ipset=/proxy.music.qq.com/oversea -ipset=/proxymc.qq.com/oversea -ipset=/ip2.kugou.com/oversea -ipset=/ip.kugou.com/oversea -ipset=/client.api.ttpod.com/oversea -ipset=/mobi.kuwo.cn/oversea -ipset=/mobilefeedback.kugou.com/oversea -ipset=/tingapi.ting.baidu.com/oversea -ipset=/music.baidu.com/oversea -ipset=/serviceinfo.sdk.duomi.com/oversea -ipset=/music.163.com/oversea -ipset=/www.xiami.com/oversea -ipset=/spark.api.xiami.com/oversea -ipset=/iplocation.geo.qiyi.com/oversea -ipset=/sns.video.qq.com/oversea -ipset=/v5.pc.duomi.com/oversea -ipset=/tms.is.ysten.com/oversea -ipset=/internal.check.duokanbox.com/oversea -ipset=/openapi.youku.com/oversea -ipset=/y.qq.com/oversea diff --git a/luci-app-ssr-plus/root/etc/uci-defaults/luci-ssr-plus b/luci-app-ssr-plus/root/etc/uci-defaults/luci-ssr-plus index a8da2f663..81bcc6609 100644 --- a/luci-app-ssr-plus/root/etc/uci-defaults/luci-ssr-plus +++ b/luci-app-ssr-plus/root/etc/uci-defaults/luci-ssr-plus @@ -31,15 +31,18 @@ touch /etc/ssrplus/china_ssr.txt touch /etc/ssrplus/deny.list touch /etc/ssrplus/white.list touch /etc/ssrplus/black.list -touch /etc/ssrplus/netflix.list -touch /etc/ssrplus/netflixip.list touch /etc/ssrplus/gfw_base.conf touch /etc/ssrplus/gfw_list.conf -touch /etc/ssrplus/oversea_list.conf touch /etc/ssrplus/ad.conf touch /etc/config/shadowsocksr if [ -s "/etc/config/shadowsocksr" ]; then + if ! uci -q get shadowsocksr.@server_subscribe_item[0] > /dev/null; then + sid_output="$(uci add shadowsocksr server_subscribe_item)" + uci -q set shadowsocksr.$sid_output.enabled=1 + uci -q set shadowsocksr.$sid_output.alias="Subscribe default" + fi + if uci -q get shadowsocksr.@server_subscribe[0].auto_update_time > /dev/null; then uci -q delete shadowsocksr.@server_subscribe[0].auto_update_time fi @@ -50,16 +53,20 @@ if [ -s "/etc/config/shadowsocksr" ]; then uci -q set shadowsocksr.@server_subscribe[0].auto_update_min_time='0' fi - if ! uci -q get shadowsocksr.@server_subscribe[0].ss_type > /dev/null; then - uci -q set shadowsocksr.@server_subscribe[0].ss_type='ss-rust' + if ! uci -q get shadowsocksr.@server_subscribe[0].config_auto_update_mode > /dev/null; then + uci -q set shadowsocksr.@server_subscribe[0].config_auto_update_mode='0' + fi + + if ! uci -q get shadowsocksr.@server_subscribe[0].config_update_interval > /dev/null; then + uci -q set shadowsocksr.@server_subscribe[0].config_update_interval='60' fi if ! uci -q get shadowsocksr.@server_subscribe[0].user_agent > /dev/null; then uci -q set shadowsocksr.@server_subscribe[0].user_agent='v2rayN/9.99' fi - if ! uci -q get shadowsocksr.@server_subscribe[0].xray_hy2_type > /dev/null; then - uci -q set shadowsocksr.@server_subscribe[0].xray_hy2_type='hysteria2' + if ! uci -q get shadowsocksr.@server_subscribe[0].proxy > /dev/null; then + uci -q set shadowsocksr.@server_subscribe[0].proxy='1' fi if ! uci -q get shadowsocksr.@global_xray_fragment[0] > /dev/null; then @@ -68,8 +75,61 @@ if [ -s "/etc/config/shadowsocksr" ]; then uci -q set shadowsocksr.@global_xray_fragment[0].noise='0' fi - uci -q commit shadowsocksr -fi + if ! uci -q get shadowsocksr.@global[0].component_mirror > /dev/null; then + uci -q set shadowsocksr.@global[0].component_mirror='direct' + fi + + if ! uci -q get shadowsocksr.@global[0].filter_aaaa > /dev/null; then + legacy_filter_aaaa="$(uci -q get shadowsocksr.@global[0].mosdns_ipv6)" + uci -q set shadowsocksr.@global[0].filter_aaaa="${legacy_filter_aaaa:-1}" + fi + + legacy_dns_mode="$(uci -q get shadowsocksr.@global[0].pdnsd_enable)" + if [ "$legacy_dns_mode" = "2" ] || [ "$legacy_dns_mode" = "3" ]; then + uci -q set shadowsocksr.@global[0].pdnsd_enable='1' + fi + + if uci -q get shadowsocksr.@global[0].mosdns_ipv6 > /dev/null; then + uci -q delete shadowsocksr.@global[0].mosdns_ipv6 + fi + + if ! uci -q get shadowsocksr.@http_proxy[0] > /dev/null; then + uci -q add shadowsocksr http_proxy + uci -q set shadowsocksr.@http_proxy[0].server='nil' + uci -q set shadowsocksr.@http_proxy[0].local_port='3128' + uci -q set shadowsocksr.@http_proxy[0].http_auth='none' + fi + + ss_rust_bin="$(command -v ssserver 2>/dev/null)" + [ -n "$ss_rust_bin" ] || [ ! -x /usr/libexec/ssserver ] || ss_rust_bin="/usr/libexec/ssserver" + mihomo_bin="$(command -v mihomo 2>/dev/null)" + [ -n "$mihomo_bin" ] || [ ! -x /usr/libexec/mihomo ] || mihomo_bin="/usr/libexec/mihomo" + xray_bin="$(command -v xray 2>/dev/null)" + [ -n "$xray_bin" ] || [ ! -x /usr/libexec/xray ] || xray_bin="/usr/libexec/xray" + for section in $(uci -q show shadowsocksr | sed -n "s/^shadowsocksr\\.\\([^.=][^.=]*\\)=servers$/\\1/p"); do + node_type="$(uci -q get shadowsocksr.${section}.type)" + case "$node_type" in + ss|ss-libev) + if [ -n "$mihomo_bin" ]; then + uci -q set shadowsocksr.${section}.type='ss' + elif [ -n "$ss_rust_bin" ]; then + uci -q set shadowsocksr.${section}.type='ss-rust' + elif [ -n "$xray_bin" ]; then + uci -q set shadowsocksr.${section}.type='v2ray' + uci -q set shadowsocksr.${section}.v2ray_protocol='shadowsocks' + fi + ;; + v2ray) + if [ "$(uci -q get shadowsocksr.${section}.v2ray_protocol)" = "shadowsocks" ] && [ -n "$mihomo_bin" ]; then + uci -q set shadowsocksr.${section}.type='ss' + uci -q delete shadowsocksr.${section}.v2ray_protocol + fi + ;; + esac + done + + uci -q commit shadowsocksr + fi [ -s "/etc/config/shadowsocksr" ] || /etc/init.d/shadowsocksr reset diff --git a/luci-app-ssr-plus/root/usr/bin/ssr-monitor b/luci-app-ssr-plus/root/usr/bin/ssr-monitor index 51f9efc40..d9732d259 100755 --- a/luci-app-ssr-plus/root/usr/bin/ssr-monitor +++ b/luci-app-ssr-plus/root/usr/bin/ssr-monitor @@ -10,6 +10,58 @@ LOCK_FILE="/var/lock/ssr-monitor.lock" [ -f "$LOCK_FILE" ] && exit 2 touch "$LOCK_FILE" + +ps_list() { + if busybox ps -w >/dev/null 2>&1; then + busybox ps -w + else + busybox ps + fi +} + +match_proc_count() { + local pattern="$1" + ps_list | grep -E "$pattern" | grep -v grep | wc -l +} + +redir_tcp_running() { + local ssr_count + local ipt_count + ssr_count=$(match_proc_count 'ssr-retcp') + ipt_count=$(ps_list | grep 'ipt2socks' | grep -v grep | grep -E ' -T( |$)| --tcp-only( |$)' | wc -l) + echo $((ssr_count + ipt_count)) +} + +redir_udp_running() { + local ssr_count + local ipt_count + ssr_count=$(match_proc_count 'ssr-reudp') + ipt_count=$(ps_list | grep 'ipt2socks' | grep -v grep | grep -E ' -U( |$)| --udp-only( |$)' | wc -l) + echo $((ssr_count + ipt_count)) +} + +kill_matched_procs() { + local pattern="$1" + ps_list | grep -E "$pattern" | grep -v grep | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 +} + +get_expected_threads() { + local threads + threads=$(uci_get_by_type global threads 0) + if [ "$threads" = "0" ] || [ -z "$threads" ]; then + threads=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null) + fi + + case "$threads" in + ''|*[!0-9]*) + threads=1 + ;; + esac + + [ "$threads" -lt 1 ] && threads=1 + echo "$threads" +} + server_process_count=$1 redir_tcp_process=$2 redir_udp_process=$3 @@ -21,6 +73,8 @@ if [ -z "$pdnsd_process" ]; then fi i=0 GLOBAL_SERVER=$(uci_get_by_type global global_server) +GLOBAL_TYPE=$(uci_get_by_name $GLOBAL_SERVER type) +EXPECTED_THREADS=$(get_expected_threads) server=$(uci_get_by_name $GLOBAL_SERVER server) kcp_port=$(uci_get_by_name $GLOBAL_SERVER kcp_port) server_port=$(uci_get_by_name $GLOBAL_SERVER server_port) @@ -32,8 +86,12 @@ while [ "1" == "1" ]; do #死循环 sleep 000030s #redir tcp if [ "$redir_tcp_process" -gt 0 ]; then - icount=$(busybox ps -w | grep ssr-retcp | grep -v grep | wc -l) - if [ "$icount" == 0 ]; then + icount=$(redir_tcp_running) + local tcp_expected="$redir_tcp_process" + if [ "$GLOBAL_TYPE" = "socks5" ]; then + tcp_expected="$EXPECTED_THREADS" + fi + if [ "$icount" -lt "$tcp_expected" ]; then logger -t "$NAME" "ssrplus redir tcp error.restart!" echolog "ssrplus redir tcp error.restart!" /etc/init.d/shadowsocksr restart @@ -42,8 +100,12 @@ while [ "1" == "1" ]; do #死循环 fi #redir udp if [ "$redir_udp_process" -gt 0 ]; then - icount=$(busybox ps -w | grep ssr-reudp | grep -v grep | wc -l) - if [ "$icount" == 0 ]; then + icount=$(redir_udp_running) + local udp_expected="$redir_udp_process" + if [ "$GLOBAL_TYPE" = "socks5" ]; then + udp_expected="$EXPECTED_THREADS" + fi + if [ "$icount" -lt "$udp_expected" ]; then logger -t "$NAME" "ssrplus redir udp error.restart!" echolog "ssrplus redir udp error.restart!" /etc/init.d/shadowsocksr restart @@ -52,18 +114,18 @@ while [ "1" == "1" ]; do #死循环 fi #server if [ "$server_process_count" -gt 0 ]; then - icount=$(busybox ps -w | grep ssr-server | grep -v grep | wc -l) + icount=$(match_proc_count 'ssr-server|ss-server-|xray-server-') if [ "$icount" -lt "$server_process_count" ]; then #如果进程挂掉就重启它 logger -t "$NAME" "ssrplus server error.restart!" echolog "ssrplus server error.restart!" - kill -9 $(busybox ps -w | grep ssr-server | grep -v grep | awk '{print $1}') >/dev/null 2>&1 + kill_matched_procs 'ssr-server|ss-server-' /etc/init.d/shadowsocksr restart exit 0 fi fi #kcptun if [ "$kcp_process" -gt 0 ]; then - icount=$(busybox ps -w | grep kcptun-client | grep -v grep | wc -l) + icount=$(ps_list | grep kcptun-client | grep -v grep | wc -l) if [ "$icount" -lt "$kcp_process" ]; then #如果进程挂掉就重启它 logger -t "$NAME" "ssrplus kcptun error.restart!" echolog "ssrplus kcptun error.restart!" @@ -73,132 +135,43 @@ while [ "1" == "1" ]; do #死循环 fi #localsocks if [ "$local_process" -gt 0 ]; then - icount=$(busybox ps -w | grep ssr-local | grep -v grep | wc -l) + icount=$(match_proc_count 'ssr-local|ss-local|tuic-local') if [ "$icount" -lt "$local_process" ]; then #如果进程挂掉就重启它 logger -t "$NAME" "global socks server error.restart!" echolog "global socks server error.restart!" - kill -9 $(busybox ps -w | grep ssr-local | grep -v grep | awk '{print $1}') >/dev/null 2>&1 + kill_matched_procs 'ssr-local|ss-local|tuic-local' /etc/init.d/shadowsocksr restart exit 0 fi fi #dns2tcp if [ "$pdnsd_process" -eq 1 ]; then - icount=$(busybox ps -w | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | wc -l) + icount=$(ps_list | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | wc -l) if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它 logger -t "$NAME" "dns2tcp tunnel error.restart!" echolog "dns2tcp tunnel error.restart!" dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53) - kill -9 $(busybox ps -w | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | awk '{print $1}') >/dev/null 2>&1 - ln_start_bin $(first_type dns2tcp) dns2tcp -L "127.0.0.1#$dns_port" -R "${dnsserver/:/#}" - fi - #dns2socks - elif [ "$pdnsd_process" -eq 2 ]; then - icount=$(busybox ps -w | grep -e ssrplus-dns -e "dns2socks 127.0.0.1 $tmp_dns_port" | grep -v grep | wc -l) - if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它 - logger -t "$NAME" "dns2socks $dnsserver tunnel error.restart!" - echolog "dns2socks $dnsserver tunnel error.restart!" - dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53) - kill -9 $(busybox ps -w | grep ssrplus-dns | grep -v grep | awk '{print $1}') >/dev/null 2>&1 - kill -9 $(busybox ps -w | grep "dns2socks 127.0.0.1 $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1 - ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_dns_port ssrplus-dns - ln_start_bin $(first_type dns2socks) dns2socks 127.0.0.1:$tmp_dns_port $dnsserver 127.0.0.1:$dns_port -q - fi - #dns2socks-rust - elif [ "$pdnsd_process" -eq 3 ]; then - icount=$(busybox ps -w | grep -e ssrplus-dns -e "dns2socks-rust -s socks5://127.0.0.1 $tmp_dns_port" | grep -v grep | wc -l) - if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它 - logger -t "$NAME" "dns2socks-rust $dnsserver tunnel error.restart!" - echolog "dns2socks-rust $dnsserver tunnel error.restart!" - dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53) - kill -9 $(busybox ps -w | grep ssrplus-dns | grep -v grep | awk '{print $1}') >/dev/null 2>&1 - kill -9 $(busybox ps -w | grep "dns2socks-rust -s socks5://127.0.0.1 $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1 - ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_dns_port ssrplus-dns - ln_start_bin $(first_type dns2socks) dns2socks-rust -s socks5://127.0.0.1:$tmp_dns_port -d $dnsserver -l 127.0.0.1:$dns_port -f -c + kill -9 $(ps_list | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | awk '{print $1}') >/dev/null 2>&1 + start_dns2tcp "$dnsserver" fi #mosdns elif [ "$pdnsd_process" -eq 4 ]; then - icount=$(busybox ps -w | grep $TMP_BIN_PATH/mosdns | grep -v grep | wc -l) + icount=$(ps_list | grep $TMP_BIN_PATH/mosdns | grep -v grep | wc -l) if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它 logger -t "$NAME" "mosdns tunnel error.restart!" echolog "mosdns tunnel error.restart!" dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53) - kill -9 $(busybox ps -w | grep $TMP_BIN_PATH/mosdns | grep -v grep | awk '{print $1}') >/dev/null 2>&1 + kill -9 $(ps_list | grep $TMP_BIN_PATH/mosdns | grep -v grep | awk '{print $1}') >/dev/null 2>&1 ln_start_bin $(first_type mosdns) mosdns start -c /etc/mosdns/config.yaml - #dnsproxy - elif [ "$pdnsd_process" -eq 5 ]; then - icount=$(busybox ps -w | grep -e ssrplus-dns -e "dnsproxy -l 127.0.0.1 -p $tmp_dns_port" | grep -v grep | wc -l) - if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它 - logger -t "$NAME" "dnsproxy $dnsserver tunnel error.restart!" - echolog "dnsproxy $dnsserver tunnel error.restart!" - local dnsproxy_dnsserver="$(uci_get_by_type global parse_method)" - if [ -n "$dnsproxy_dnsserver" ] && [ "$dnsproxy_dnsserver" != "parse_file" ]; then - dnsserver="$(uci_get_by_type global dnsproxy_tunnel_forward 8.8.4.4:53)" - fi - kill -9 $(busybox ps -w | grep "dnsproxy -l 127.0.0.1 -p $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1 - dnsproxy_ipv6="$(uci_get_by_type global dnsproxy_ipv6)" - disabled_ipv6="--ipv6-disabled" - fi - if [ "$dnsproxy_dnsserver" != "parse_file" ]; then - ln_start_bin $(first_type dnsproxy) dnsproxy -l 127.0.0.1 -p $tmp_dns_port -p $dns_port -u $dnsserver $disabled_ipv6 --cache --cache-min-ttl=3600 - else - dnsproxy_dnsserver_file="$TMP_PATH/dnsproxy_dns.list" - cleaned_file="$TMP_PATH/cleaned_dns.list" - temp_file="$TMP_PATH/temp_dns.list" - > "$cleaned_file" - # 清理输入文件并去重 - while IFS= read -r line || [ -n "$line" ]; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - [ -z "$line" ] && continue - echo "$line" | grep -qE '^#' && continue - echo "$line" >> "$cleaned_file" - done < "/etc/ssrplus/dnsproxy_dns.list" - # 获取清理后文件的MD5 - cleaned_md5=$(md5sum "$cleaned_file" | awk '{print $1}') - if [ ! -f "$dnsproxy_dnsserver_file" ]; then - cp "$cleaned_file" "$dnsproxy_dnsserver_file" - else - target_md5=$(md5sum "$dnsproxy_dnsserver_file" | awk '{print $1}') - if [ "$cleaned_md5" != "$target_md5" ]; then - > "$temp_file" - # 保留目标文件中也存在于清理文件的记录(去重) - while IFS= read -r line; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - if grep -qixF "$line" "$cleaned_file" && ! grep -qixF "$line" "$temp_file"; then - echo "$line" >> "$temp_file" - fi - done < "$dnsproxy_dnsserver_file" - # 添加清理文件中有但目标文件没有的记录(去重) - while IFS= read -r line; do - line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//') - if ! grep -qixF "$line" "$temp_file"; then - echo "$line" >> "$temp_file" - fi - done < "$cleaned_file" - temp_md5=$(md5sum "$temp_file" | awk '{print $1}') - if [ "$temp_md5" != "$target_md5" ]; then - mv "$temp_file" "$dnsproxy_dnsserver_file" - else - rm -f "$temp_file" - fi - fi - fi - rm -f "$cleaned_file" - - if [ -n "$dnsproxy_dnsserver_file" ] && [ -s "$dnsproxy_dnsserver_file" ]; then - local upstreams_logic_mode="$(uci_get_by_type global upstreams_logic_mode)" - ln_start_bin $(first_type dnsproxy) dnsproxy -l 127.0.0.1 -p $tmp_dns_port -p $dns_port -u $dnsproxy_dnsserver_file $disabled_ipv6 --cache --cache-min-ttl=3600 --upstream-mode=$upstreams_logic_mode - fi - fi fi #chinadns-ng(proxy) elif [ "$pdnsd_process" -eq 6 ]; then - icount=$(busybox ps -w | grep -e ssrplus-dns -e "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | wc -l) + icount=$(ps_list | grep -e ssrplus-dns -e "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | wc -l) if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它 logger -t "$NAME" "chinadns-ng $dnsserver tunnel error.restart!" echolog "chinadns-ng $dnsserver tunnel error.restart!" dnsserver=$(uci_get_by_type global chinadns_ng_tunnel_forward 8.8.4.4:53) - kill -9 $(busybox ps -w | grep "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1 + kill -9 $(ps_list | grep "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1 local chinadns_ng_proto="$(uci_get_by_type global chinadns_ng_proto)" local chinadns_ng_dns="" IFS=',' @@ -224,10 +197,18 @@ while [ "1" == "1" ]; do #死循环 dnsserver="$chinadns_ng_dns" ln_start_bin $(first_type chinadns-ng) chinadns-ng -b 127.0.0.1 -l $tmp_dns_port -l $dns_port -p 3 -d gfw $dnsserver -N --filter-qtype 64,65 -f -r --cache 4096 --cache-stale 86400 --cache-refresh 20 fi + #built-in dns + elif [ "$pdnsd_process" -eq 7 ]; then + if ! is_builtin_dns_active; then + logger -t "$NAME" "builtin dns error.restart!" + echolog "builtin dns error.restart!" + /etc/init.d/shadowsocksr restart + exit 0 + fi fi #chinadns-ng(china) if [ "$(uci -q get "dhcp.@dnsmasq[0]._unused_ssrp_changed")" = "1" ]; then - icount=$(busybox ps -w | grep $TMP_BIN_PATH/chinadns-ng | grep -v grep | wc -l) + icount=$(ps_list | grep $TMP_BIN_PATH/chinadns-ng | grep -v grep | wc -l) if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它 logger -t "$NAME" "chinadns-ng tunnel error.restart!" echolog "chinadns-ng tunnel error.restart!" @@ -237,7 +218,7 @@ while [ "1" == "1" ]; do #死循环 "wan") chinadns="$wandns" ;; ""|"wan_114") chinadns="$wandns,114.114.114.114" ;; esac - kill -9 $(busybox ps -w | grep $TMP_BIN_PATH/chinadns-ng | grep -v grep | awk '{print $1}') >/dev/null 2>&1 + kill -9 $(ps_list | grep $TMP_BIN_PATH/chinadns-ng | grep -v grep | awk '{print $1}') >/dev/null 2>&1 ln_start_bin $(first_type chinadns-ng) chinadns-ng -l $china_dns_port -4 china -p 3 -c ${chinadns/:/#} -t 127.0.0.1#$dns_port -N -f -r fi fi diff --git a/luci-app-ssr-plus/root/usr/bin/ssr-rules b/luci-app-ssr-plus/root/usr/bin/ssr-rules index 47da8b98d..de945169c 100755 --- a/luci-app-ssr-plus/root/usr/bin/ssr-rules +++ b/luci-app-ssr-plus/root/usr/bin/ssr-rules @@ -85,13 +85,14 @@ usage() { -e extra options for iptables -o apply the rules to the OUTPUT chain -O apply the global rules to the OUTPUT chain - -u enable udprelay mode, TPROXY is required - -U enable udprelay mode, using different IP - and ports for TCP and UDP - -f flush the rules + -u enable udprelay mode, TPROXY is required + -U enable udprelay mode, using different IP + and ports for TCP and UDP + -y disable all auxiliary UDP rules when UDP + transparent proxy is unavailable + -f flush the rules -g gfwlist mode -r router mode - -c oversea mode -z all mode # New persistence management options (use different letters to avoid conflicts) @@ -274,7 +275,7 @@ flush_nftables() { # Optional: force delete all ss_spec related sets (even if table was accidentally deleted) for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router \ - china fplan bplan gmlan oversea whitelist blacklist netflix gfwlist music; do + china fplan bplan gmlan whitelist blacklist gfwlist music; do $NFT delete set inet ss_spec $setname 2>/dev/null $NFT delete set ip ss_spec_mangle $setname 2>/dev/null done @@ -310,7 +311,7 @@ flush_iptables_legacy() { ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null fi for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router \ - china fplan bplan gmlan oversea whitelist blacklist netflix gfwlist music; do + china fplan bplan gmlan whitelist blacklist gfwlist music; do ipset -X $setname 2>/dev/null done [ -n "$FWI" ] && echo '#!/bin/sh' >$FWI @@ -333,7 +334,7 @@ ipset_nft() { fi # Create necessary collections - for setname in china gmlan fplan bplan whitelist blacklist netflix music; do + for setname in china gmlan fplan bplan whitelist blacklist music; do if ! $NFT list set inet ss_spec $setname >/dev/null 2>&1; then $NFT add set inet ss_spec $setname '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null else @@ -392,13 +393,6 @@ ipset_nft() { $NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp ip daddr @music return fi - # Shunt/Netflix rules - if [ -f "$SHUNT_LIST" ]; then - for ip in $(cat "$SHUNT_LIST" 2>/dev/null); do - [ -n "$ip" ] && $NFT add element inet ss_spec netflix "{ $ip }" 2>/dev/null - done - fi - # Set up mode-specific rules case "$RUNMODE" in router) @@ -427,14 +421,6 @@ ipset_nft() { $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @gfwlist jump ss_spec_wan_fw $NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw ;; - oversea) - if ! $NFT list set inet ss_spec oversea >/dev/null 2>&1; then - $NFT add set inet ss_spec oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null - fi - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @oversea jump ss_spec_wan_fw - $NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan jump ss_spec_wan_fw - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china jump ss_spec_wan_fw - ;; all) if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then $NFT add rule inet ss_spec ss_spec_wan_ac jump ss_spec_wan_fw @@ -450,9 +436,10 @@ ipset_iptables() { $IPT -N SS_SPEC_WAN_AC 2>/dev/null $IPT -F SS_SPEC_WAN_AC + $IPT -I SS_SPEC_WAN_AC -m mark --mark 255 -j RETURN $IPT -I SS_SPEC_WAN_AC -p tcp --dport 53 -d 127.0.0.0/8 -j RETURN - $IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d "$server" -j RETURN + [ -n "$server" ] && $IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d "$server" -j RETURN ipset -N gmlan hash:net 2>/dev/null for ip in $LAN_GM_IP; do ipset -! add gmlan "$ip"; done @@ -474,12 +461,6 @@ ipset_iptables() { $IPT -A SS_SPEC_WAN_AC -m set --match-set gfwlist dst -j SS_SPEC_WAN_FW $IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -m set ! --match-set china dst -j SS_SPEC_WAN_FW ;; - oversea) - ipset -N oversea hash:net 2>/dev/null - $IPT -I SS_SPEC_WAN_AC -m set --match-set oversea dst -j SS_SPEC_WAN_FW - $IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -j SS_SPEC_WAN_FW - $IPT -A SS_SPEC_WAN_AC -m set --match-set china dst -j SS_SPEC_WAN_FW - ;; all) $IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW ;; @@ -510,25 +491,6 @@ ipset_iptables() { for ip in $WAN_BP_IP; do ipset -! add whitelist "$ip"; done for ip in $WAN_FW_IP; do ipset -! add blacklist "$ip"; done - - if [ "$SHUNT_PORT" != "0" ]; then - ipset -N netflix hash:net 2>/dev/null - for ip in $(cat "${SHUNT_LIST:=/dev/null}" 2>/dev/null); do ipset -! add netflix "$ip"; done - case "$SHUNT_PORT" in - 0) ;; - 1) - $IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$local_port" - ;; - *) - $IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$SHUNT_PORT" - if [ "$SHUNT_PROXY" = "1" ]; then - $IPT -I SS_SPEC_WAN_AC -p tcp -d "$SHUNT_IP" -j REDIRECT --to-ports "$local_port" - else - ipset -! add whitelist "$SHUNT_IP" - fi - ;; - esac - fi return $? } @@ -562,22 +524,6 @@ fw_rule_nft() { fi fi - if [ "$SHUNT_PORT" != "0" ] && [ -f "$SHUNT_LIST" ]; then - case "$SHUNT_PORT" in - 1) - $NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$local_port - ;; - *) - $NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$SHUNT_PORT - if [ "$SHUNT_PROXY" = "1" ]; then - $NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr $SHUNT_IP counter redirect to :$local_port - else - [ -n "$SHUNT_IP" ] && $NFT add element inet ss_spec whitelist "{ $SHUNT_IP }" 2>/dev/null - fi - ;; - esac - fi - return $? } @@ -670,7 +616,7 @@ ac_rule_nft() { fi # Block UDP port 443 when TPROXY not Enable - if [ -z "$TPROXY" ]; then + if [ -z "$TPROXY" ] && [ "$DISABLE_UDP_RULES" != "1" ]; then # Add UDP 443 block rule if [ -z "$Interface" ]; then if [ -n "$MATCH_SET" ]; then @@ -717,10 +663,11 @@ ac_rule_nft() { case "$OUTPUT" in 1) # Create ss_spec_output tcp chain - if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then - $NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }' - fi - $NFT flush chain inet ss_spec ss_spec_output 2>/dev/null + if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then + $NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }' + fi + $NFT flush chain inet ss_spec ss_spec_output 2>/dev/null + $NFT add rule inet ss_spec ss_spec_output meta mark 255 return 2>/dev/null # Exclude special local addresses if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then @@ -741,10 +688,11 @@ ac_rule_nft() { ;; 2) # Create ss_spec_output tcp chain - if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then - $NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }' - fi - $NFT flush chain inet ss_spec ss_spec_output 2>/dev/null + if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then + $NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }' + fi + $NFT flush chain inet ss_spec ss_spec_output 2>/dev/null + $NFT add rule inet ss_spec ss_spec_output meta mark 255 return 2>/dev/null # Exclude special local addresses if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then @@ -758,11 +706,12 @@ ac_rule_nft() { for ip in $(gen_spec_iplist); do [ -n "$ip" ] && $NFT add element inet ss_spec ssr_gen_router "{ $ip }" 2>/dev/null done - if ! $NFT list chain inet ss_spec ss_spec_router >/dev/null 2>&1; then - $NFT add chain inet ss_spec ss_spec_router 2>/dev/null - fi - $NFT flush chain inet ss_spec ss_spec_router 2>/dev/null - $NFT add rule inet ss_spec ss_spec_router ip daddr @ssr_gen_router return 2>/dev/null + if ! $NFT list chain inet ss_spec ss_spec_router >/dev/null 2>&1; then + $NFT add chain inet ss_spec ss_spec_router 2>/dev/null + fi + $NFT flush chain inet ss_spec ss_spec_router 2>/dev/null + $NFT add rule inet ss_spec ss_spec_router meta mark 255 return 2>/dev/null + $NFT add rule inet ss_spec ss_spec_router ip daddr @ssr_gen_router return 2>/dev/null $NFT add rule inet ss_spec ss_spec_router jump ss_spec_wan_fw 2>/dev/null $NFT add rule inet ss_spec ss_spec_output $TCP_EXT_ARGS jump ss_spec_router comment "\"$TAG\"" 2>/dev/null ;; @@ -792,7 +741,7 @@ ac_rule_iptables() { EOF # Block UDP port 443 when TPROXY not Enable - if [ -z "$TPROXY" ]; then + if [ -z "$TPROXY" ] && [ "$DISABLE_UDP_RULES" != "1" ]; then # Add UDP 443 block rule if [ -z "$Interface" ]; then # Global rules @@ -845,10 +794,11 @@ ac_rule_iptables() { create ssr_gen_router hash:net $(gen_spec_iplist | sed -e "s/^/add ssr_gen_router /") EOF - $IPT -N SS_SPEC_ROUTER 2>/dev/null - $IPT -F SS_SPEC_ROUTER 2>/dev/null - $IPT -A SS_SPEC_ROUTER -m set --match-set ssr_gen_router dst -j RETURN && \ - $IPT -A SS_SPEC_ROUTER -j SS_SPEC_WAN_FW + $IPT -N SS_SPEC_ROUTER 2>/dev/null + $IPT -F SS_SPEC_ROUTER 2>/dev/null + $IPT -A SS_SPEC_ROUTER -m mark --mark 255 -j RETURN && \ + $IPT -A SS_SPEC_ROUTER -m set --match-set ssr_gen_router dst -j RETURN && \ + $IPT -A SS_SPEC_ROUTER -j SS_SPEC_WAN_FW $IPT -I OUTPUT 1 -p tcp -m comment --comment "$TAG" -j SS_SPEC_ROUTER ;; esac @@ -1006,14 +956,6 @@ tp_rule_nft() { $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @gfwlist counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null ;; - oversea) - if ! $NFT list set ip ss_spec_mangle oversea >/dev/null 2>&1; then - $NFT add set ip ss_spec_mangle oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null - fi - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @oversea counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @china counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null - ;; all) $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null ;; @@ -1090,8 +1032,8 @@ tp_rule_iptables() { do $ipt -A SS_SPEC_TPROXY -p udp -d "$net" -j RETURN done - $ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN - [ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER" + [ -n "$SERVER" ] && $ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN + [ -n "$SERVER" ] && [ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER" if [ -f "${xhttp_ip:=/etc/ssrplus/xhttp_address.txt}" ]; then while IFS= read -r ip; do [ -n "$ip" ] && ipset add whitelist "$ip" -exist @@ -1119,11 +1061,6 @@ tp_rule_iptables() { $ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set gfwlist dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK} $ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set ! --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK} ;; - oversea) - $ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set oversea src -m dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK} - $ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK} - $ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK} - ;; all) $ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK} ;; @@ -1495,7 +1432,7 @@ restore_from_persistence() { fi } -while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczAKPCRXh" arg; do +while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUyfgrzAKPCRXh" arg; do case "$arg" in m) Interface=$OPTARG @@ -1563,15 +1500,15 @@ while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczAKPCRXh" arg; do U) TPROXY=2 ;; + y) + DISABLE_UDP_RULES=1 + ;; g) RUNMODE=gfw ;; r) RUNMODE=router ;; - c) - RUNMODE=oversea - ;; z) RUNMODE=all ;; @@ -1703,7 +1640,7 @@ runmode_change() { } # Main process -if [ -n "$server" ] && [ -n "$local_port" ]; then +if [ -n "$local_port" ]; then if ! echo "$local_port" | grep -qE '^[0-9]+$'; then loger 3 "Invalid local port: $local_port" exit 1 @@ -1715,7 +1652,6 @@ if [ -n "$server" ] && [ -n "$local_port" ]; then LOCAL_PORT=$local_port ;; 2) - : ${SERVER:?"You must assign an ip for the udp relay server."} : ${LOCAL_PORT:?"You must assign a port for the udp relay server."} ;; esac diff --git a/luci-app-ssr-plus/root/usr/bin/ssr-switch b/luci-app-ssr-plus/root/usr/bin/ssr-switch index 5a2a37090..3aab3190f 100755 --- a/luci-app-ssr-plus/root/usr/bin/ssr-switch +++ b/luci-app-ssr-plus/root/usr/bin/ssr-switch @@ -1,4 +1,4 @@ -#!/bin/sh /etc/rc.common +#!/bin/sh # # Copyright (C) 2017 openwrt-ssr # Copyright (C) 2017 yushi studio @@ -7,149 +7,276 @@ # See /LICENSE for more information. # +ORIG_ARG1="${1:-}" +ORIG_ARG2="${2:-}" +ORIG_ARG3="${3:-}" +[ -f /lib/functions.sh ] && . /lib/functions.sh . $IPKG_INSTROOT/etc/init.d/shadowsocksr LOCK_FILE="/var/lock/ssr-switch.lock" -[ -f "$LOCK_FILE" ] && exit 2 -touch "$LOCK_FILE" -LOG_FILE=/var/log/ssrplus.log +PROBE_SID="" cycle_time=60 switch_time=3 -normal_flag=0 -server_locate=0 -server_count=0 -ENABLE_SERVER=nil -[ -n "$1" ] && cycle_time=$1 -[ -n "$2" ] && switch_time=$2 -DEFAULT_SERVER=$(uci_get_by_type global global_server) -CURRENT_SERVER=$DEFAULT_SERVER +probe_socks_port=10800 +direct_fail_count=0 +direct_fail_limit=3 +probe_url="$(uci_get_by_type server_subscribe url_test_url)" +[ -n "$probe_url" ] || probe_url="https://www.google.com/generate_204" +PROBE_INSTANCE_KEY="ssr-switch-probe-local" -#判断代理是否正常 -check_proxy() { - local result=0 - local try_count=$(uci_get_by_type global switch_try_count 3) - for i in $(seq 1 $try_count); do - /usr/bin/ssr-check www.google.com 80 $switch_time 1 - if [ "$?" == "0" ]; then - # echolog "Check Google Proxy Success, count=$i" - result=0 - break - else - # echolog "Check Google Proxy Fail, count=$i" - /usr/bin/ssr-check www.baidu.com 80 $switch_time 1 - if [ "$?" == "0" ]; then - result=1 - else - result=2 - fi - fi +DEFAULT_SERVER="$(uci_get_by_type global global_server)" +CURRENT_SERVER="$DEFAULT_SERVER" +ENABLE_SERVER="nil" + +cleanup_probe_socks() { + ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill >/dev/null 2>&1 + sleep 1 + ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 + rm -f "$TMP_PATH/tcp-http-ssr-switch-local.json" 2>/dev/null + rm -rf "$TMP_PATH/ss-${PROBE_INSTANCE_KEY}" "$TMP_PATH/tuic-${PROBE_INSTANCE_KEY}" 2>/dev/null + rm -f /tmp/ssr-switch-probe.log 2>/dev/null +} + +start_probe_socks() { + local sid="$1" + local server_type + + server_type="$(uci_get_by_name "$sid" type)" + [ "$server_type" = "clash" ] && return 1 + [ "$server_type" = "tuic" ] && return 1 + [ "$server_type" = "socks5" ] && return 1 + + cleanup_probe_socks + LOCAL_SERVER="$sid" + local_config_file="$TMP_PATH/tcp-http-ssr-switch-local.json" + _local="2" + local_enable=0 + tmp_local_port= + mode="tcp,udp" + + if [ "$server_type" = "v2ray" ]; then + export SSR_SWITCH_PROBE=1 + gen_config_file "$LOCAL_SERVER" "$server_type" 4 0 "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1 + unset SSR_SWITCH_PROBE + local xray_bin + xray_bin="$(first_type xray)" + [ -x "$xray_bin" ] || return 1 + "$xray_bin" run -c "$local_config_file" >>/tmp/ssr-switch-probe.log 2>&1 & + else + export SSR_SWITCH_PROBE=1 + export SSR_SWITCH_PROBE_INSTANCE_KEY="$PROBE_INSTANCE_KEY" + start_local_with_port "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1 + unset SSR_SWITCH_PROBE_INSTANCE_KEY + unset SSR_SWITCH_PROBE + fi + + sleep 2 + return 0 +} + +probe_http_ok() { + case "$1" in + [1-5][0-9][0-9]) + return 0 + ;; + esac + return 1 +} + +check_direct_probe() { + local code + + code="$(curl -k -sS -L -o /dev/null \ + --connect-timeout "$switch_time" \ + --max-time "$switch_time" \ + -w '%{http_code}' \ + "$probe_url" 2>/dev/null || true)" + probe_http_ok "$code" +} + +check_proxy_via_socks() { + local try_count + local code + local proxy_host + + try_count="$(uci_get_by_type global switch_try_count 3)" + for i in $(seq 1 "$try_count"); do + for proxy_host in "127.0.0.1:$probe_socks_port" "[::1]:$probe_socks_port"; do + code="$(curl -k -sS -L -o /dev/null \ + --connect-timeout "$switch_time" \ + --max-time "$switch_time" \ + --socks5-hostname "$proxy_host" \ + -w '%{http_code}' \ + "$probe_url" 2>/dev/null || true)" + probe_http_ok "$code" && return 0 + done sleep 1 done - return $result + return 1 } test_proxy() { - local servername=$(uci_get_by_name $1 server) - local serverport=$(uci_get_by_name $1 server_port) - ipset add whitelist $servername 2>/dev/null - tcping -q -c 3 -i 1 -t 2 -p $serverport $servername - if [ "$?" -gt "0" ]; then - ipset del whitelist $servername 2>/dev/null + local sid="$1" + [ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1 + [ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1 + start_probe_socks "$sid" || { + cleanup_probe_socks return 1 - fi - /usr/bin/ssr-check $servername $serverport $switch_time + } + check_proxy_via_socks local ret=$? - ipset del whitelist $servername 2>/dev/null - if [ "$ret" == "0" ]; then - return 0 - else - return 1 - fi + cleanup_probe_socks + return $ret } search_proxy() { - let server_count=server_count+1 - [ "$normal_flag" == "1" -a "$server_count" -le "$server_locate" ] && return 0 - [ "$(uci_get_by_name $1 switch_enable 0)" != "1" ] && return 1 - [ $ENABLE_SERVER != nil ] && return 0 - [ "$1" == "$CURRENT_SERVER" ] && return 0 - local servername=$(uci_get_by_name $1 server) - local serverport=$(uci_get_by_name $1 server_port) - ipset add whitelist $servername 2>/dev/null - /usr/bin/ssr-check $servername $serverport $switch_time - local ret=$? - ipset del whitelist $servername 2>/dev/null - if [ "$ret" == "0" ]; then - server_locate=$server_count - ENABLE_SERVER=$1 + local sid="$1" + [ "$(uci_get_by_name "$sid" switch_enable 0)" = "1" ] || return 1 + [ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1 + [ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1 + [ "$sid" = "$CURRENT_SERVER" ] && return 1 + [ "$ENABLE_SERVER" != "nil" ] && return 0 + + if test_proxy "$sid"; then + ENABLE_SERVER="$sid" return 0 - else - return 1 fi + return 1 } -#选择可用的代理 select_proxy() { - config_load $NAME - ENABLE_SERVER=nil - mkdir -p /var/run /var/etc - server_count=0 + config_load "$NAME" + ENABLE_SERVER="nil" config_foreach search_proxy servers } -#切换代理 +set_main_server() { + local sid="$1" + [ -n "$sid" ] || return 1 + uci set shadowsocksr.@global[0].global_server="$sid" + uci commit shadowsocksr + return 0 +} + switch_proxy() { - /etc/init.d/shadowsocksr restart $1 + set_main_server "$1" || return 1 + /etc/init.d/shadowsocksr restart "$1" return 0 } start() { - #不支持kcptun启用时的切换 - [ $(uci_get_by_name $DEFAULT_SERVER kcp_enable) = "1" ] && return 1 - while [ "1" == "1" ]; do #死循环 - sleep 0000$cycle_time - LOGTIME=$(date "+%Y-%m-%d %H:%M:%S") - #判断当前代理是否为缺省服务器 - if [ "$CURRENT_SERVER" != "$DEFAULT_SERVER" ]; then - #echo "not default proxy" - echolog "Current server is not default Main server, try to switch back." - #检查缺省服务器是否正常 - if test_proxy $DEFAULT_SERVER; then - #echo "switch to default proxy" - echolog "Main server is avilable." - #缺省服务器正常,切换回来 - CURRENT_SERVER=$DEFAULT_SERVER - switch_proxy $CURRENT_SERVER - echolog "switch to default "$(uci_get_by_name $CURRENT_SERVER alias)" proxy!" - else - echolog "Main server is NOT avilable.Continue using current server." - fi - fi - #判断当前代理是否正常 - #echolog "Start checking if the current server is available." - check_proxy - current_ret=$? - if [ "$current_ret" == "1" ]; then - #当前代理错误,判断有无可用的服务器 - #echo "current error" - echolog "Current server error, try to switch another server." - select_proxy - if [ "$ENABLE_SERVER" != nil ]; then - #有其他服务器可用,进行切换 - #echo $(uci_get_by_name $new_proxy server) - echolog "Another server is avilable, now switching server." - CURRENT_SERVER=$ENABLE_SERVER - switch_proxy $CURRENT_SERVER - normal_flag=1 - echolog "Switch to "$(uci_get_by_name $CURRENT_SERVER alias)" proxy!" - else - switch_proxy $CURRENT_SERVER - normal_flag=1 - echolog "Try restart current server." - fi - else - normal_flag=0 - # echolog "ShadowsocksR No Problem." - fi + while [ "1" = "1" ]; do + sleep "0000$cycle_time" + run_once done } + +run_once() { + DEFAULT_SERVER="$(uci_get_by_type global global_server)" + [ "$DEFAULT_SERVER" = "nil" ] && { + direct_fail_count=0 + return 0 + } + [ "$(uci_get_by_name "$DEFAULT_SERVER" type)" = "clash" ] && { + direct_fail_count=0 + cleanup_probe_socks + return 0 + } + [ "$(uci_get_by_name "$DEFAULT_SERVER" type)" = "tuic" ] && { + direct_fail_count=0 + cleanup_probe_socks + return 0 + } + [ "$(uci_get_by_name "$DEFAULT_SERVER" kcp_enable 0)" = "1" ] && { + direct_fail_count=0 + cleanup_probe_socks + return 1 + } + + CURRENT_SERVER="$DEFAULT_SERVER" + + if check_direct_probe; then + direct_fail_count=0 + return 0 + fi + + direct_fail_count=$((direct_fail_count + 1)) + [ "$direct_fail_count" -lt "$direct_fail_limit" ] && return 0 + direct_fail_count=0 + + if test_proxy "$CURRENT_SERVER"; then + return 0 + fi + + echolog "Current server error, try to switch another server." + select_proxy + if [ "$ENABLE_SERVER" != "nil" ]; then + CURRENT_SERVER="$ENABLE_SERVER" + switch_proxy "$CURRENT_SERVER" + echolog "Switch to $(uci_get_by_name "$CURRENT_SERVER" alias) proxy!" + else + switch_proxy "$CURRENT_SERVER" + echolog "Try restart current server." + fi + return 0 +} + +probe_once() { + local sid="$1" + [ -n "$sid" ] || { + echo "missing_sid" + return 1 + } + cleanup_probe_socks + if ! start_probe_socks "$sid"; then + echo "probe_start:fail" + return 1 + fi + echo "probe_start:ok" + echo "probe:curl_ipv4" + curl -k -sS -L -o /dev/null \ + --connect-timeout "$switch_time" \ + --max-time "$switch_time" \ + --socks5-hostname "127.0.0.1:$probe_socks_port" \ + -w 'code=%{http_code}\n' \ + "$probe_url" 2>/dev/null || true + echo "probe:curl_ipv6" + curl -g -k -sS -L -o /dev/null \ + --connect-timeout "$switch_time" \ + --max-time "$switch_time" \ + --socks5-hostname "[::1]:$probe_socks_port" \ + -w 'code=%{http_code}\n' \ + "$probe_url" 2>/dev/null || true + cleanup_probe_socks + return 0 +} + +main() { + case "${ORIG_ARG1:-}" in + probe) + PROBE_SID="${ORIG_ARG2:-}" + probe_once "$PROBE_SID" + ;; + once) + run_once + ;; + start) + [ -n "$ORIG_ARG2" ] && cycle_time="$ORIG_ARG2" + [ -n "$ORIG_ARG3" ] && switch_time="$ORIG_ARG3" + [ -f "$LOCK_FILE" ] && exit 2 + touch "$LOCK_FILE" + start + ;; + *) + [ -n "$ORIG_ARG1" ] && cycle_time="$ORIG_ARG1" + [ -n "$ORIG_ARG2" ] && switch_time="$ORIG_ARG2" + [ -f "$LOCK_FILE" ] && exit 2 + touch "$LOCK_FILE" + start + ;; + esac +} + +if [ "${0##*/}" = "ssr-switch" ]; then + main "$@" +fi diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/Country.mmdb b/luci-app-ssr-plus/root/usr/share/shadowsocksr/Country.mmdb new file mode 100644 index 000000000..6898cab5c Binary files /dev/null and b/luci-app-ssr-plus/root/usr/share/shadowsocksr/Country.mmdb differ diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua new file mode 100644 index 000000000..701bfacc7 --- /dev/null +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua @@ -0,0 +1,939 @@ +#!/usr/bin/lua + +require "nixio.fs" +require "luci.model.uci" + +local ok_lyaml, lyaml = pcall(require, "lyaml") +if not ok_lyaml then + io.stderr:write("lyaml_not_found\n") + os.exit(2) +end + +local uci = require "luci.model.uci".cursor() +local ok_jsonc, jsonc = pcall(require, "luci.jsonc") + +local function read_file(path) + local data = nixio.fs.readfile(path) + if not data or data == "" then + return nil + end + return data +end + +local function write_file(path, data) + return nixio.fs.writefile(path, data) +end + +local function load_yaml(path) + local raw = read_file(path) + if not raw then + return nil, "read_failed" + end + + local ok, parsed = pcall(lyaml.load, raw) + if not ok or type(parsed) ~= "table" then + return nil, "parse_failed" + end + + return parsed +end + +local function dump_yaml(path, data) + local ok, rendered = pcall(lyaml.dump, { data }) + if not ok or not rendered then + return nil, "dump_failed" + end + + write_file(path, rendered) + return true +end + +local function split_filter_words(text) + local items = {} + for part in tostring(text or ""):gmatch("[^/]+") do + if part ~= "" then + items[#items + 1] = part + end + end + return items +end + +local function trim(value) + return tostring(value or ""):gsub("^%s+", ""):gsub("%s+$", "") +end + +local function parse_csv_line(line) + local cols = {} + local cur = "" + local in_quote = false + local i = 1 + + while i <= #line do + local ch = line:sub(i, i) + if ch == '"' then + if in_quote and line:sub(i + 1, i + 1) == '"' then + cur = cur .. '"' + i = i + 1 + else + in_quote = not in_quote + end + elseif ch == "," and not in_quote then + cols[#cols + 1] = cur + cur = "" + else + cur = cur .. ch + end + i = i + 1 + end + + cols[#cols + 1] = cur + return cols +end + +local function read_clash_client_rules_csv(sid) + local rows = {} + sid = trim(sid) + if sid == "" then + return rows + end + + local csv_path = string.format("/etc/ssrplus/clash/%s.csv", sid) + local raw = read_file(csv_path) + if not raw or raw == "" then + return rows + end + + local first = true + for line in tostring(raw):gsub("\r", ""):gmatch("[^\n]+") do + local text = trim(line) + if text ~= "" then + if first and text:lower() == "enabled,client,policy,remarks,client_mac" then + first = false + else + local cols = parse_csv_line(line) + if #cols >= 4 then + rows[#rows + 1] = { + enabled = cols[1], + ip_addr = trim(cols[2] or ""), + policy_group = trim(cols[3] or ""), + remarks = trim(cols[4] or ""), + client_mac = trim(cols[5] or "") + } + end + end + end + end + + return rows +end + +local function has_proxy_sections(doc) + return type(doc.proxies) == "table" or type(doc["proxy-providers"]) == "table" +end + +local function validate(path) + local doc = load_yaml(path) + if not doc then + return false + end + return has_proxy_sections(doc) +end + +local function filter(path, filter_words) + local doc, err = load_yaml(path) + if not doc then + io.stderr:write(err or "parse_failed", "\n") + return false + end + + local words = split_filter_words(filter_words) + if #words == 0 then + return true + end + + local removed = {} + local proxies = {} + for _, proxy in ipairs(doc.proxies or {}) do + local name = tostring(proxy.name or "") + local matched = false + for _, word in ipairs(words) do + if name:find(word, 1, true) then + matched = true + removed[name] = true + break + end + end + if not matched then + proxies[#proxies + 1] = proxy + end + end + doc.proxies = proxies + + for _, group in ipairs(doc["proxy-groups"] or {}) do + if type(group.proxies) == "table" then + local kept = {} + for _, name in ipairs(group.proxies) do + if not removed[tostring(name)] then + kept[#kept + 1] = name + end + end + group.proxies = kept + end + end + + local count = 0 + for _ in pairs(removed) do + count = count + 1 + end + + dump_yaml(path, doc) + io.stdout:write(tostring(count), "\n") + return true +end + +local function deep_merge(dst, src) + if type(dst) ~= "table" or type(src) ~= "table" then + return src + end + + for k, v in pairs(src) do + if type(v) == "table" and type(dst[k]) == "table" then + dst[k] = deep_merge(dst[k], v) + else + dst[k] = v + end + end + + return dst +end + +local function strip_runtime_conflicts(doc) + doc.tun = nil + doc.listeners = nil + doc["redir-port"] = nil + doc["tproxy-port"] = nil + doc["socks-port"] = nil + doc["mixed-port"] = nil + doc.port = nil + doc["external-controller"] = nil + doc.secret = nil + doc["allow-lan"] = nil + if type(doc.dns) == "table" then + doc.dns["fake-ip-range"] = nil + doc.dns["fake-ip-filter"] = nil + end +end + +local function group_requires_candidates(group) + local gtype = tostring(group and group.type or ""):lower() + return gtype == "select" + or gtype == "fallback" + or gtype == "load-balance" + or gtype == "url-test" + or gtype == "relay" +end + +local function has_nonempty_sequence(value) + return type(value) == "table" and next(value) ~= nil +end + +local function fill_empty_proxy_groups(doc) + local changed = 0 + for _, group in ipairs(doc["proxy-groups"] or {}) do + if type(group) == "table" + and group_requires_candidates(group) + and not has_nonempty_sequence(group.proxies) + and not has_nonempty_sequence(group.use) + then + group.proxies = { "DIRECT" } + changed = changed + 1 + end + end + return changed +end + +local function strip_incompatible_script_rules(doc) + local kept = {} + local removed = 0 + local has_script_rule = false + + for _, rule in ipairs(doc.rules or {}) do + local text = tostring(rule or "") + if text:match("^SCRIPT,") then + removed = removed + 1 + else + kept[#kept + 1] = rule + if text:match("^SCRIPT,") then + has_script_rule = true + end + end + end + + if removed > 0 then + doc.rules = kept + end + + if not has_script_rule then + doc.script = nil + end + + return removed +end + +local function prepare(input_path, output_path) + local doc, err = load_yaml(input_path) + if not doc then + io.stderr:write(err or "parse_failed", "\n") + return false + end + if not has_proxy_sections(doc) then + io.stderr:write("missing_proxy_sections\n") + return false + end + + strip_runtime_conflicts(doc) + local filled_groups = fill_empty_proxy_groups(doc) + local stripped_rules = strip_incompatible_script_rules(doc) + local ok, rendered = pcall(lyaml.dump, { doc }) + if not ok or not rendered then + io.stderr:write("dump_failed\n") + return false + end + + write_file(output_path, rendered) + io.stdout:write(string.format("filled_groups=%d stripped_script_rules=%d\n", filled_groups, stripped_rules)) + return true +end + +local function merge(raw_path, overlay_path, output_path) + local raw_doc, raw_err = load_yaml(raw_path) + if not raw_doc then + io.stderr:write(raw_err or "parse_failed", "\n") + return false + end + + local overlay_doc, overlay_err = load_yaml(overlay_path) + if not overlay_doc then + io.stderr:write(overlay_err or "parse_failed", "\n") + return false + end + + strip_runtime_conflicts(raw_doc) + local filled_groups = fill_empty_proxy_groups(raw_doc) + local stripped_rules = strip_incompatible_script_rules(raw_doc) + local merged = deep_merge(raw_doc, overlay_doc) + local ok, rendered = pcall(lyaml.dump, { merged }) + if not ok or not rendered then + io.stderr:write("dump_failed\n") + return false + end + + write_file(output_path, rendered) + io.stdout:write(string.format("filled_groups=%d stripped_script_rules=%d\n", filled_groups, stripped_rules)) + return true +end + +local function append_client_policy_rules(runtime_path, sid) + local doc, err = load_yaml(runtime_path) + if not doc then + io.stderr:write(err or "parse_failed", "\n") + return false + end + + local valid_policies = {} + for _, proxy in ipairs(doc.proxies or {}) do + if type(proxy) == "table" and proxy.name and proxy.name ~= "" then + valid_policies[tostring(proxy.name)] = true + end + end + for _, group in ipairs(doc["proxy-groups"] or {}) do + if type(group) == "table" and group.name and group.name ~= "" then + valid_policies[tostring(group.name)] = true + end + end + + local custom_rules = {} + for _, section in ipairs(read_clash_client_rules_csv(sid)) do + if tostring(section.enabled or "0") == "1" then + local ip_addr = tostring(section.ip_addr or "") + local policy_group = tostring(section.policy_group or "") + if ip_addr ~= "" and policy_group ~= "" and valid_policies[policy_group] then + if not ip_addr:find("/", 1, true) then + ip_addr = ip_addr .. "/32" + end + custom_rules[#custom_rules + 1] = string.format("SRC-IP-CIDR,%s,%s", ip_addr, policy_group) + end + end + end + + if #custom_rules == 0 then + io.stdout:write("client_rules=0\n") + return true + end + + local existing_rules = {} + for _, rule in ipairs(doc.rules or {}) do + local text = tostring(rule or "") + if not text:match("^SRC%-IP%-CIDR,") then + existing_rules[#existing_rules + 1] = rule + end + end + + doc.rules = {} + for _, rule in ipairs(custom_rules) do + doc.rules[#doc.rules + 1] = rule + end + for _, rule in ipairs(existing_rules) do + doc.rules[#doc.rules + 1] = rule + end + + local ok, rendered = pcall(lyaml.dump, { doc }) + if not ok or not rendered then + io.stderr:write("dump_failed\n") + return false + end + + write_file(runtime_path, rendered) + io.stdout:write(string.format("client_rules=%d\n", #custom_rules)) + return true +end + +local function bool_enabled(value) + return value == "1" or value == 1 or value == true or value == "true" +end + +local function split_csv(value) + local items = {} + for part in tostring(value or ""):gmatch("[^,%s]+") do + items[#items + 1] = part + end + return items +end + +local function get_server_field(sid, option, default) + local value = uci:get("shadowsocksr", sid, option) + if value == nil or value == "" then + return default + end + return value +end + +local function get_filter_aaaa() + local value = uci:get_first("shadowsocksr", "global", "filter_aaaa", "1") + if value == nil or value == "" then + value = uci:get_first("shadowsocksr", "global", "mosdns_ipv6", "1") + end + return value +end + +local function build_tuic_runtime_doc(sid, local_port, socks_port, mode) + local server = get_server_field(sid, "server", "") + local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0 + local tuic_ip = get_server_field(sid, "tuic_ip", "") + local tls_host = get_server_field(sid, "tls_host", "") + local ipstack_prefer = get_server_field(sid, "ipstack_prefer", "") + local dns_mode = uci:get_first("shadowsocksr", "global", "pdnsd_enable", "0") + + local proxy = { + name = sid, + type = "tuic", + server = server, + port = server_port, + uuid = get_server_field(sid, "tuic_uuid", ""), + password = get_server_field(sid, "tuic_passwd", ""), + ["udp-relay-mode"] = get_server_field(sid, "udp_relay_mode", "native"), + ["congestion-controller"] = get_server_field(sid, "congestion_control", "cubic"), + ["skip-cert-verify"] = bool_enabled(get_server_field(sid, "insecure", "0")), + ["disable-sni"] = bool_enabled(get_server_field(sid, "disable_sni", "0")), + ["reduce-rtt"] = bool_enabled(get_server_field(sid, "zero_rtt_handshake", "0")) + } + + if tuic_ip ~= "" then + proxy.ip = tuic_ip + end + if tls_host ~= "" then + proxy.sni = tls_host + end + + local alpn = split_csv(get_server_field(sid, "tuic_alpn", "")) + if #alpn > 0 then + proxy.alpn = alpn + end + + local heartbeat = tonumber(get_server_field(sid, "heartbeat", "0")) + if heartbeat and heartbeat > 0 then + proxy["heartbeat-interval"] = heartbeat * 1000 + end + + local timeout = tonumber(get_server_field(sid, "timeout", "0")) + if timeout and timeout > 0 then + proxy["request-timeout"] = timeout * 1000 + end + + local max_udp_packet_size = tonumber(get_server_field(sid, "tuic_max_package_size", "0")) + if max_udp_packet_size and max_udp_packet_size > 0 then + proxy["max-udp-relay-packet-size"] = max_udp_packet_size + end + + if ipstack_prefer ~= "" then + proxy["ip-version"] = ipstack_prefer == "v6first" and "ipv6-prefer" or "ipv4-prefer" + end + + local listen_port = tonumber(local_port) + local socks_listen = tonumber(socks_port) + + local doc = { + ["allow-lan"] = true, + ["bind-address"] = "0.0.0.0", + mode = "rule", + ["log-level"] = "silent", + ["find-process-mode"] = "off", + ["unified-delay"] = true, + ["tcp-concurrent"] = true, + ["routing-mark"] = 255, + proxies = { proxy }, + ["proxy-groups"] = { + { + name = "PROXY", + type = "select", + proxies = { sid } + } + }, + rules = { "MATCH,PROXY" }, + tun = { enable = false }, + profile = { ["store-selected"] = true }, + dns = { + enable = dns_mode == "7", + ["enhanced-mode"] = "redir-host", + listen = "127.0.0.1:5335", + ipv6 = get_filter_aaaa() ~= "1" + } + } + + if mode == "socks" then + doc["socks-port"] = listen_port + else + doc["redir-port"] = listen_port + doc["tproxy-port"] = listen_port + if socks_listen and socks_listen > 0 then + doc["socks-port"] = socks_listen + end + end + + return doc +end + +local function generate_tuic_runtime(sid, output_path, local_port, socks_port, mode) + local doc = build_tuic_runtime_doc(sid, local_port, socks_port, mode) + local ok, rendered = pcall(lyaml.dump, { doc }) + if not ok or not rendered then + io.stderr:write("dump_failed\n") + return false + end + write_file(output_path, rendered) + return true +end + +local function parse_plugin_opts(value) + local result = {} + for part in tostring(value or ""):gmatch("[^;]+") do + local key, val = part:match("^%s*([^=]+)=?(.*)%s*$") + if key and key ~= "" then + result[key] = val or "" + end + end + return result +end + +local function split_host_port(value) + local text = tostring(value or "") + if text == "" then + return "", "" + end + local host, port = text:match("^%[(.-)%]:(%d+)$") + if host and port then + return host, port + end + host, port = text:match("^(.-):(%d+)$") + if host and port then + return host, port + end + return text, "" +end + +local function bool_default(value, default) + if value == nil or value == "" then + return default + end + return bool_enabled(value) +end + +local function number_or_nil(value) + if value == nil or value == "" then + return nil + end + return tonumber(value) +end + +local function string_or_nil(value) + if value == nil or value == "" then + return nil + end + return tostring(value) +end + +local function pick_plugin_opt(plugin_opts, ...) + for i = 1, select("#", ...) do + local key = select(i, ...) + local value = plugin_opts[key] + if value ~= nil and value ~= "" then + return value + end + end + return nil +end + +local function parse_plugin_headers(plugin_opts) + local headers = {} + local raw_headers = pick_plugin_opt(plugin_opts, "headers", "header") + + if raw_headers and ok_jsonc and jsonc then + local decoded = jsonc.parse(raw_headers) + if type(decoded) == "table" then + for key, value in pairs(decoded) do + headers[tostring(key)] = tostring(value) + end + end + end + + if raw_headers and next(headers) == nil then + for part in tostring(raw_headers):gmatch("[^|,]+") do + local key, value = part:match("^%s*([^=:]+)%s*[:=]%s*(.-)%s*$") + if key and key ~= "" and value and value ~= "" then + headers[key] = value + end + end + end + + for key, value in pairs(plugin_opts) do + local header_name = key:match("^headers[%.:](.+)$") + or key:match("^header[%.:](.+)$") + or key:match("^header_(.+)$") + if header_name and header_name ~= "" and value ~= "" then + headers[header_name] = value + end + end + + return next(headers) and headers or nil +end + +local function get_plugin_client_fingerprint(sid, plugin_opts) + return string_or_nil( + pick_plugin_opt( + plugin_opts, + "client-fingerprint", + "client_fingerprint", + "fingerprint" + ) or get_server_field(sid, "fingerprint", "") + ) +end + +local function normalize_plugin_name(plugin) + local value = tostring(plugin or ""):lower() + if value == "" or value == "none" then + return "" + end + if value == "simple-obfs" then + return "obfs-local" + end + if value == "obfs" then + return "obfs-local" + end + if value == "shadowtls" then + return "shadow-tls" + end + if value == "gost" then + return "gost-plugin" + end + if value == "kcp-tun" then + return "kcptun" + end + return value +end + +local function build_shadowsocks_plugin(proxy, sid) + local plugin = normalize_plugin_name(get_server_field(sid, "plugin", "")) + local plugin_opts = parse_plugin_opts(get_server_field(sid, "plugin_opts", "")) + + if plugin == "" then + return + end + + if plugin == "obfs-local" then + proxy.plugin = "obfs" + proxy["plugin-opts"] = { + mode = pick_plugin_opt(plugin_opts, "obfs", "mode") or "http", + host = string_or_nil(pick_plugin_opt(plugin_opts, "obfs-host", "obfs_host", "host")) + } + return + end + + if plugin == "v2ray-plugin" or plugin == "xray-plugin" then + proxy.plugin = "v2ray-plugin" + proxy["plugin-opts"] = { + mode = pick_plugin_opt(plugin_opts, "mode") or "websocket", + tls = bool_default(pick_plugin_opt(plugin_opts, "tls"), false), + fingerprint = string_or_nil(pick_plugin_opt(plugin_opts, "fingerprint")), + ["skip-cert-verify"] = bool_default(pick_plugin_opt(plugin_opts, "skip-cert-verify", "skip_cert_verify", "insecure"), false), + host = string_or_nil(pick_plugin_opt(plugin_opts, "host")), + path = string_or_nil(pick_plugin_opt(plugin_opts, "path")), + mux = bool_default(pick_plugin_opt(plugin_opts, "mux"), false), + headers = parse_plugin_headers(plugin_opts), + ["v2ray-http-upgrade"] = bool_default(pick_plugin_opt(plugin_opts, "v2ray-http-upgrade", "v2ray_http_upgrade"), false) + } + return + end + + if plugin == "gost-plugin" then + proxy.plugin = "gost-plugin" + proxy["plugin-opts"] = { + mode = pick_plugin_opt(plugin_opts, "mode") or "websocket", + tls = bool_default(pick_plugin_opt(plugin_opts, "tls"), false), + fingerprint = string_or_nil(pick_plugin_opt(plugin_opts, "fingerprint")), + ["skip-cert-verify"] = bool_default(pick_plugin_opt(plugin_opts, "skip-cert-verify", "skip_cert_verify", "insecure"), false), + host = string_or_nil(pick_plugin_opt(plugin_opts, "host")), + path = string_or_nil(pick_plugin_opt(plugin_opts, "path")), + mux = bool_default(pick_plugin_opt(plugin_opts, "mux"), false), + headers = parse_plugin_headers(plugin_opts) + } + return + end + + if plugin == "shadow-tls" then + local host, port = split_host_port(pick_plugin_opt(plugin_opts, "host") or "") + local version + if plugin_opts.v3 == "1" or plugin_opts.version == "3" then + version = 3 + elseif plugin_opts.v2 == "1" or plugin_opts.version == "2" then + version = 2 + elseif plugin_opts.v1 == "1" or plugin_opts.version == "1" then + version = 1 + end + proxy.plugin = "shadow-tls" + proxy["client-fingerprint"] = get_plugin_client_fingerprint(sid, plugin_opts) + proxy["plugin-opts"] = { + host = host ~= "" and host or nil, + port = number_or_nil(port), + password = string_or_nil(pick_plugin_opt(plugin_opts, "passwd", "password")), + version = version + } + return + end + + if plugin == "restls" then + proxy.plugin = "restls" + proxy["client-fingerprint"] = get_plugin_client_fingerprint(sid, plugin_opts) + proxy["plugin-opts"] = { + host = string_or_nil(pick_plugin_opt(plugin_opts, "host")), + password = string_or_nil(pick_plugin_opt(plugin_opts, "passwd", "password")), + ["version-hint"] = string_or_nil(pick_plugin_opt(plugin_opts, "version-hint", "version_hint")), + ["restls-script"] = string_or_nil(pick_plugin_opt(plugin_opts, "restls-script", "restls_script")) + } + return + end + + if plugin == "kcptun" then + proxy.plugin = "kcptun" + proxy["plugin-opts"] = { + key = string_or_nil(pick_plugin_opt(plugin_opts, "key", "passwd", "password")), + crypt = string_or_nil(pick_plugin_opt(plugin_opts, "crypt")), + mode = string_or_nil(pick_plugin_opt(plugin_opts, "mode")), + conn = number_or_nil(pick_plugin_opt(plugin_opts, "conn")), + autoexpire = number_or_nil(pick_plugin_opt(plugin_opts, "autoexpire")), + scavengettl = number_or_nil(pick_plugin_opt(plugin_opts, "scavengettl")), + mtu = number_or_nil(pick_plugin_opt(plugin_opts, "mtu")), + ratelimit = number_or_nil(pick_plugin_opt(plugin_opts, "ratelimit")), + sndwnd = number_or_nil(pick_plugin_opt(plugin_opts, "sndwnd")), + rcvwnd = number_or_nil(pick_plugin_opt(plugin_opts, "rcvwnd")), + datashard = number_or_nil(pick_plugin_opt(plugin_opts, "datashard")), + parityshard = number_or_nil(pick_plugin_opt(plugin_opts, "parityshard")), + dscp = number_or_nil(pick_plugin_opt(plugin_opts, "dscp")), + nocomp = bool_default(pick_plugin_opt(plugin_opts, "nocomp"), false), + acknodelay = bool_default(pick_plugin_opt(plugin_opts, "acknodelay"), false), + nodelay = number_or_nil(pick_plugin_opt(plugin_opts, "nodelay")), + interval = number_or_nil(pick_plugin_opt(plugin_opts, "interval")), + resend = number_or_nil(pick_plugin_opt(plugin_opts, "resend")), + sockbuf = number_or_nil(pick_plugin_opt(plugin_opts, "sockbuf")), + smuxver = number_or_nil(pick_plugin_opt(plugin_opts, "smuxver")), + smuxbuf = number_or_nil(pick_plugin_opt(plugin_opts, "smuxbuf")), + framesize = number_or_nil(pick_plugin_opt(plugin_opts, "framesize")), + streambuf = number_or_nil(pick_plugin_opt(plugin_opts, "streambuf")), + keepalive = number_or_nil(pick_plugin_opt(plugin_opts, "keepalive")) + } + return + end + + proxy.plugin = plugin + if next(plugin_opts) then + proxy["plugin-opts"] = plugin_opts + end +end + +local function build_kcptun_plugin(proxy, sid) + if not bool_enabled(get_server_field(sid, "kcp_enable", "0")) then + return + end + + proxy.plugin = "kcptun" + proxy.port = tonumber(get_server_field(sid, "kcp_port", "0")) or proxy.port + proxy["plugin-opts"] = { + key = get_server_field(sid, "kcp_password", ""), + mode = "fast", + mtu = 1350 + } +end + +local function build_shadowsocks_runtime_doc(sid, local_port, socks_port, mode) + local dns_mode = uci:get_first("shadowsocksr", "global", "pdnsd_enable", "0") + local server = get_server_field(sid, "server", "") + local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0 + local method = get_server_field(sid, "encrypt_method_ss", "none") + local password = get_server_field(sid, "password", "") + local proxy = { + name = sid, + type = "ss", + server = server, + port = server_port, + cipher = method, + password = password, + udp = true, + tfo = bool_enabled(get_server_field(sid, "fast_open", "0")) + } + + if get_server_field(sid, "type", "") == "ss" then + build_kcptun_plugin(proxy, sid) + end + if proxy.plugin == nil then + build_shadowsocks_plugin(proxy, sid) + end + + local doc = { + ["allow-lan"] = true, + ["bind-address"] = "0.0.0.0", + mode = "rule", + ["log-level"] = "silent", + ["find-process-mode"] = "off", + ["unified-delay"] = true, + ["tcp-concurrent"] = true, + ["routing-mark"] = 255, + proxies = { proxy }, + ["proxy-groups"] = { + { + name = "PROXY", + type = "select", + proxies = { sid } + } + }, + rules = { "MATCH,PROXY" }, + tun = { enable = false }, + profile = { ["store-selected"] = true }, + dns = { + enable = dns_mode == "7", + ["enhanced-mode"] = "redir-host", + listen = "127.0.0.1:5335", + ipv6 = get_filter_aaaa() ~= "1" + } + } + + local listen_port = tonumber(local_port) + local socks_listen = tonumber(socks_port) + if mode == "socks" then + doc["socks-port"] = listen_port + else + doc["redir-port"] = listen_port + doc["tproxy-port"] = listen_port + if socks_listen and socks_listen > 0 then + doc["socks-port"] = socks_listen + end + end + + return doc +end + +local function generate_shadowsocks_runtime(sid, output_path, local_port, socks_port, mode) + local doc = build_shadowsocks_runtime_doc(sid, local_port, socks_port, mode) + local ok, rendered = pcall(lyaml.dump, { doc }) + if not ok or not rendered then + io.stderr:write("dump_failed\n") + return false + end + write_file(output_path, rendered) + return true +end + +local function build_shadowsocks_server_doc(sid) + local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0 + local method = get_server_field(sid, "encrypt_method_ss", "aes-128-gcm") + local password = get_server_field(sid, "password", "") + local listener = { + name = sid, + type = "shadowsocks", + listen = "::", + port = server_port, + cipher = method, + password = password, + udp = true, + tfo = bool_enabled(get_server_field(sid, "fast_open", "0")) + } + + local plugin = normalize_plugin_name(get_server_field(sid, "plugin", "")) + if plugin == "obfs-local" then + local plugin_opts = parse_plugin_opts(get_server_field(sid, "plugin_opts", "")) + listener.obfs = plugin_opts.obfs or plugin_opts.mode or "http" + listener.obfs_opts = { + mode = plugin_opts.obfs or plugin_opts.mode or "http", + host = plugin_opts["obfs-host"] or plugin_opts.obfs_host or plugin_opts.host or nil + } + end + + return { + ["allow-lan"] = true, + ["bind-address"] = "*", + ["log-level"] = "silent", + ["find-process-mode"] = "off", + listeners = { listener } + } +end + +local function generate_shadowsocks_server(sid, output_path) + local doc = build_shadowsocks_server_doc(sid) + local ok, rendered = pcall(lyaml.dump, { doc }) + if not ok or not rendered then + io.stderr:write("dump_failed\n") + return false + end + write_file(output_path, rendered) + return true +end + +local action = arg[1] +if action == "validate" then + os.exit(validate(arg[2]) and 0 or 1) +elseif action == "filter" then + os.exit(filter(arg[2], arg[3]) and 0 or 1) +elseif action == "prepare" then + os.exit(prepare(arg[2], arg[3]) and 0 or 1) +elseif action == "merge" then + os.exit(merge(arg[2], arg[3], arg[4]) and 0 or 1) +elseif action == "append_client_policy_rules" then + os.exit(append_client_policy_rules(arg[2], arg[3]) and 0 or 1) +elseif action == "tuic" then + os.exit(generate_tuic_runtime(arg[2], arg[3], arg[4], arg[5], arg[6]) and 0 or 1) +elseif action == "ss" then + os.exit(generate_shadowsocks_runtime(arg[2], arg[3], arg[4], arg[5], arg[6]) and 0 or 1) +elseif action == "ss_server" then + os.exit(generate_shadowsocks_server(arg[2], arg[3]) and 0 or 1) +else + io.stderr:write("usage: clash_yaml.lua validate | filter | prepare | merge | append_client_policy_rules | tuic [socks_port] [mode] | ss [socks_port] [mode] | ss_server \n") + os.exit(1) +end diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua index 94d8c0164..5beece00b 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua @@ -2,7 +2,6 @@ require "luci.sys" local ucursor = require "luci.model.uci".cursor() -local datatypes = require "luci.cbi.datatypes" local json = require "luci.jsonc" local server_section = arg[1] @@ -12,12 +11,6 @@ local socks_port = arg[4] or "0" local chain = arg[5] or "0" -local GLOBAL = { - DNS_SERVER = {}, - DNS_HOSTNAME = {}, - VPS_EXCLUDE = {} -} - -- 辅助函数:拆分字符串(若 luci.util 未加载则定义) local function split(str, pat) local t = {} @@ -38,27 +31,52 @@ local server = ucursor:get_all("shadowsocksr", server_section) local socks_server = ucursor:get_all("shadowsocksr", "@socks5_proxy[0]") or {} local xray_fragment = ucursor:get_all("shadowsocksr", "@global_xray_fragment[0]") or {} local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {} +local default_node_local_port = ucursor:get_first("shadowsocksr", "global", "default_node_local_port", "1234") +local dns_mode = ucursor:get_first("shadowsocksr", "global", "pdnsd_enable", "0") +local dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "filter_aaaa") +if not dns_ipv4_only or dns_ipv4_only == "" then + dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "mosdns_ipv6", "1") +end +local builtin_dns_server = ucursor:get_first("shadowsocksr", "global", "tunnel_forward", "8.8.4.4:53") local outbound_settings = nil local xray_version = nil local xray_version_val = 0 +local xray_builtin_dns = nil local node_id = server_section local remarks = server.alias or "" local b64decode = nixio.bin.b64decode local b64encode = nixio.bin.b64encode +local effective_node_local_port = tonumber(server.local_port) or tonumber(default_node_local_port) or 1234 --- 解析 URL(简单实现,仅用于 DoH) -local function parseURL(url) - if not url then return nil end - local schema, rest = url:match("^(https?)://(.*)$") - if not schema then return nil end - local host, port_str = rest:match("^([^:]+):?(%d*)/?.*$") - local port = tonumber(port_str) or (schema == "https" and 443 or 80) - return { host = host, port = port, schema = schema } +if server.type == "ss-rust" then + server.type = "ss" end -if server.type == "ss-rust" or server.type == "ss-libev" then - server.type = "ss" +local function parse_realm_uri(uri) + if type(uri) ~= "string" then return nil end + -- realm://token@server/realm_id?query + local token, server_url, realm_id, query = trim(uri):match("^realm://([^@]+)@([^/]+)/([^?]*)%??(.*)$") + if not token or not server_url or not realm_id then return nil end + realm_id = realm_id:gsub("/+$", "") + local realm = { + token = token, + server_url = server_url, + realm_id = realm_id + } + -- 解析 query 中的 stun= + if query and query ~= "" then + local stun_servers = {} + for key, value in query:gmatch("([^&=?]+)=([^&]+)") do + if key == "stun" and value ~= "" then + stun_servers[#stun_servers + 1] = value + end + end + if #stun_servers > 0 then + realm.stun_servers = stun_servers + end + end + return realm end -- base64 解码 @@ -120,9 +138,25 @@ local function cleanEmptyTables(t) return next(t) and t or nil end +local function format_host(host) + host = tostring(host or "") + if host ~= "" and host:find(":", 1, true) and not host:match("^%[.*%]$") then + return "[" .. host .. "]" + end + return host +end + +local function format_host_port(host, port) + host = format_host(host) + if port == nil or port == "" then + return host + end + return host .. ":" .. tostring(port) +end + -- 确保正确判断程序是否存在 local function is_finded(e) - return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= "" + return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= "" end -- 获取 Xray 版本号 @@ -203,20 +237,33 @@ function wireguard() -- 处理 reserved 字段,支持逗号分隔的数字或 Base64 编码 local reserved = nil if server.reserved then - local bytes = {} - if not server.reserved:match("[^%d,]+") then - -- 纯数字和逗号,解析为数字列表 - server.reserved:gsub("%d+", function(b) - bytes[#bytes + 1] = tonumber(b) - end) - else - -- Base64 编码的二进制数据 - local result = base64Decode(server.reserved) - for i = 1, #result do - bytes[i] = result:byte(i) + local all_bytes = {} + local reserved_values = server.reserved + + -- 确保是 table 类型 + if type(reserved_values) ~= "table" then + reserved_values = {reserved_values} + end + + for _, reserved_str in ipairs(reserved_values) do + if type(reserved_str) == "string" then + if not reserved_str:match("[^%d,]+") then + -- 数字和逗号格式 + reserved_str:gsub("%d+", function(b) + all_bytes[#all_bytes + 1] = tonumber(b) + end) + else + -- Base64 格式 + local result = base64Decode(reserved_str) + if result then + for i = 1, #result do + all_bytes[#all_bytes + 1] = result:byte(i) + end + end + end end end - reserved = #bytes > 0 and bytes or nil + reserved = #all_bytes > 0 and all_bytes or nil end outbound_settings = { @@ -226,15 +273,22 @@ function wireguard() { publicKey = server.peer_pubkey, preSharedKey = server.preshared_key, - endpoint = server.server .. ":" .. server.server_port, + endpoint = format_host_port(server.server, server.server_port), keepAlive = tonumber(server.keepaliveperiod), allowedIPs = (server.allowedips) or nil, } }, - noKernelTun = (server.kernelmode == "1") and true or false, + kernelMode = (server.kernelmode == "1") and true or false, reserved = reserved, mtu = tonumber(server.mtu) } + if server.finalmask and server.finalmask ~= "" then + local ok, fm = pcall(json.parse, base64Decode(server.finalmask)) + if ok and type(fm) == "table" then + outbound_settings.streamSettings = outbound_settings.streamSettings or {} + outbound_settings.streamSettings.finalmask = fm + end + end end function xray_hysteria2() outbound_settings = { @@ -299,6 +353,35 @@ local Xray = { -- 初始化 outbounds 表 outbounds = {}, } + +if server.type == "v2ray" and dns_mode == "7" and os.getenv("SSR_SWITCH_PROBE") ~= "1" then + local dns_host = builtin_dns_server:match("^([^:]+)") or "8.8.4.4" + local dns_port = tonumber(builtin_dns_server:match(":(%d+)$")) or 53 + + Xray.dns = { + queryStrategy = (dns_ipv4_only == "1") and "UseIPv4" or "UseIP", + servers = { + string.format("tcp://%s:%d", dns_host, dns_port) + } + } + + table.insert(Xray.inbounds, { + listen = "127.0.0.1", + port = 5335, + protocol = "dokodemo-door", + settings = { + address = dns_host, + port = dns_port, + network = "tcp,udp" + }, + tag = "builtin-dns-in" + }) + + xray_builtin_dns = { + address = dns_host, + port = dns_port + } +end -- 传入连接 -- 添加 dokodemo-door 配置,如果 local_port 不为 0 if local_port ~= "0" then @@ -349,11 +432,11 @@ if proto and proto:find("tcp") and socks_port ~= "0" then -- socks protocol = "socks", port = tonumber(socks_port), - settings = { + settings = { auth = socks_server.socks5_auth or "noauth", udp = true, - mixed = ((socks_server.socks5_mixed == '1') and true or false) or (socks_server.server == 'same') and nil, - accounts = (socks_server.server ~= "same" and (socks_server.socks5_auth and socks_server.socks5_auth ~= "noauth")) and { + mixed = ((socks_server.socks5_mixed == '1') and true or false) or nil, + accounts = (socks_server.socks5_auth and socks_server.socks5_auth ~= "noauth") and { { user = socks_server.socks5_user, pass = socks_server.socks5_pass @@ -529,7 +612,7 @@ Xray.outbounds = { local finalmask = {} local PT = server.v2ray_protocol local TP = server.transport - if server.transport == "kcp" then + if TP == "kcp" then local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat", dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"} local udp = {} @@ -547,14 +630,36 @@ Xray.outbounds = { udp[#udp+1] = c finalmask.udp = udp elseif PT == "hysteria2" then + local udp = {} if (server.flag_obfs == "1" and (server.obfs_type and server.obfs_type ~= "")) then - finalmask.udp = {{ - type = server.obfs_type, + local o = { + type = "salamander", settings = server.salamander and { - password = server.salamander + password = server.salamander, + packetSize = server.obfs_type == "gecko" and "512-1200" or nil } or nil - }} + } + udp[#udp+1] = o end + if server.hysteria2_realms then + local realm = parse_realm_uri(server.hysteria2_realm_url) + local url, stun + if realm then + if realm.token and realm.server_url and realm.realm_id then + url = "realm://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id + end + stun = realm.stun_servers or server.hysteria2_realm_stun + end + local r = { + type = "realm", + settings = { + url = url, + stunServers = stun + } + } + udp[#udp+1] = r + end + finalmask.udp = udp local up = tonumber(server.uplink_capacity) or 0 local down = tonumber(server.downlink_capacity) or 0 finalmask.quicParams = { @@ -642,7 +747,6 @@ Xray.outbounds = { end)(), sockopt = { mark = 255, - domainStrategy = server.domain_strategy or "UseIP", tcpFastOpen = (function() if server.transport == "xhttp" then return (server.fast_open == "1") and true or false @@ -670,18 +774,24 @@ Xray.outbounds = { } } -table.insert(Xray.outbounds, { - protocol = "freedom", - tag = "direct", - settings = { - domainStrategy = server.domain_strategy or "UseIP" -- 可根据需要改为 direct_dns_query_strategy - }, - streamSettings = { - sockopt = { - mark = 255 +if xray_builtin_dns then + table.insert(Xray.outbounds, { + protocol = "dns", + tag = "builtin-dns-out", + settings = { + network = "tcp", + address = xray_builtin_dns.address, + port = xray_builtin_dns.port } - } -}) + }) + Xray.routing = Xray.routing or {} + Xray.routing.rules = Xray.routing.rules or {} + table.insert(Xray.routing.rules, { + type = "field", + inboundTag = { "builtin-dns-in" }, + outboundTag = "builtin-dns-out" + }) +end -- 添加带有 fragment 设置的 dialerproxy 配置 if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.enabled ~= "0") then @@ -712,121 +822,6 @@ if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.e }) end --- Xray DNS 解析配置 -if datatypes.hostname(server.server) and server.domain_resolver and (server.domain_resolver_dns or server.domain_resolver_dns_https) then - -- 解析 DNS 服务器配置 - local dns_proto = server.domain_resolver - local config_address - local config_port - if dns_proto == "https" then - local _a = parseURL(server.domain_resolver_dns_https) - if _a then - config_address = server.domain_resolver_dns_https - config_port = _a.port or 443 - if _a.hostname and datatypes.hostname(_a.hostname) then - GLOBAL.DNS_HOSTNAME[_a.hostname] = true - end - end - else - local server_address = server.domain_resolver_dns - config_port = 53 - local parts = split(server_address, ":") - if #parts > 1 then - server_address = parts[1] - config_port = tonumber(parts[#parts]) - end - config_address = server_address - if dns_proto == "tcp" then - config_address = dns_proto .. "://" .. server_address .. ":" .. config_port - end - end - - -- 存入 GLOBAL.DNS_SERVER(去重) - local dns_key = dns_proto .. "|" .. config_address .. "|" .. tostring(config_port) - if not GLOBAL.DNS_SERVER[dns_key] then - GLOBAL.DNS_SERVER[dns_key] = { - tag = "dns-node-" .. node_id, - address = config_address, - port = config_port, - finalQuery = true, - disableCache = false, - serveStale = true, - serveExpiredTTL = 30, - domains = {} - } - end - - -- 添加当前节点域名到该 DNS 服务器的 domains 列表 - local domain = "full:" .. server.server - local exists - for _, d in ipairs(GLOBAL.DNS_SERVER[dns_key].domains) do - if d == domain then exists = true; break end - end - if not exists then - table.insert(GLOBAL.DNS_SERVER[dns_key].domains, domain) - end - GLOBAL.VPS_EXCLUDE[server.server] = true - - -- 构建 Xray.dns - local dns_servers = { "localhost" } - for key, dns_server in pairs(GLOBAL.DNS_SERVER) do - table.insert(dns_servers, { - tag = dns_server.tag, - address = dns_server.address, - port = dns_server.port, - domains = dns_server.domains, - finalQuery = dns_server.finalQuery, - serveStale = dns_server.serveStale, - serveExpiredTTL = dns_server.serveExpiredTTL, - disableCache = dns_server.disableCache - }) - end - Xray.dns = { - servers = dns_servers, - disableFallback = true, - disableFallbackIfMatch = true, - useSystemHosts = true, - queryStrategy = "UseIP", - disableCache = false, - tag = "dns-global" -- 用于 routing - } -end - --- 代理出站的 tag(与 outbound 中的 tag 保持一致) -local proxy_tag = (remarks ~= nil and remarks ~= "") and (node_id .. ":" .. remarks) or node_id --- 构建 routing 规则表 -local routing_rules = {} --- 1. 为每个自定义 DNS 服务器添加直连规则(inboundTag 匹配 dns-node-xxx) -if GLOBAL.DNS_SERVER and next(GLOBAL.DNS_SERVER) then - for _, dns_server in pairs(GLOBAL.DNS_SERVER) do - table.insert(routing_rules, { - inboundTag = { dns_server.tag }, - outboundTag = "direct" - }) - end -end - --- 2. 添加全局 DNS 直连规则(需要 Xray.dns 中已设置 tag = "dns-global") -if Xray.dns and Xray.dns.tag then - table.insert(routing_rules, { - inboundTag = { "dns-global" }, - outboundTag = "direct" - }) -end - --- 3. 添加默认规则:所有 TCP/UDP 流量走代理 -table.insert(routing_rules, { - network = "tcp,udp", - ruleTag = "default", - outboundTag = proxy_tag -}) - --- 构建 routing 对象 -Xray.routing = { - rules = routing_rules, - domainStrategy = "AsIs" -} - local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA" local cipher13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384" local trojan = { @@ -874,7 +869,7 @@ local trojan = { } } local naiveproxy = { - proxy = (server.username and server.password and server.server and server.server_port) and "https://" .. server.username .. ":" .. server.password .. "@" .. server.server .. ":" .. server.server_port, + proxy = (server.username and server.password and server.server and server.server_port) and "https://" .. server.username .. ":" .. server.password .. "@" .. format_host_port(server.server, server.server_port), listen = (proto == "redir") and "redir" .. "://0.0.0.0:" .. tonumber(local_port) or "socks" .. "://0.0.0.0:" .. tonumber(local_port), ["insecure-concurrency"] = tonumber(server.concurrency) or 1 } @@ -895,16 +890,16 @@ local hysteria2 = { server.server_port and ( server.port_range and - (server.server .. ":" .. server.server_port .. "," .. string.gsub(server.port_range, ":", "-")) + (format_host_port(server.server, server.server_port) .. "," .. string.gsub(server.port_range, ":", "-")) or - (server.server .. ":" .. server.server_port) + (format_host_port(server.server, server.server_port)) ) or ( server.port_range and - server.server .. ":" .. string.gsub(server.port_range, ":", "-") + format_host(server.server) .. ":" .. string.gsub(server.port_range, ":", "-") or - server.server and server.server .. ":443" + server.server and format_host_port(server.server, "443") ) ), bandwidth = (server.uplink_capacity or server.downlink_capacity) and { @@ -1003,7 +998,7 @@ local hysteria2 = { } local shadowtls = { client = { - server_addr = server.server_port and server.server .. ":" .. server.server_port or nil, + server_addr = server.server_port and format_host_port(server.server, server.server_port) or nil, listen = "127.0.0.1:" .. tonumber(local_port), tls_names = server.shadowtls_sni, password = server.password @@ -1017,7 +1012,7 @@ local chain_sslocal = { locals = local_port ~= "0" and { { local_address = "0.0.0.0", - local_port = (chain_local_port == "0" and tonumber(server.local_port) or tonumber(chain_local_port)), + local_port = (chain_local_port == "0" and effective_node_local_port or tonumber(chain_local_port)), mode = (proto:find("tcp,udp") and "tcp_and_udp") or proto .. "_only", protocol = "redir", tcp_redir = "redirect", @@ -1046,7 +1041,7 @@ local chain_sslocal = { local chain_vmess = { inbounds = (local_port ~= "0") and { { - port = (chain_local_port == "0" and tonumber(server.local_port) or tonumber(chain_local_port)), + port = (chain_local_port == "0" and effective_node_local_port or tonumber(chain_local_port)), protocol = "dokodemo-door", settings = { network = proto, @@ -1082,7 +1077,7 @@ local chain_vmess = { } local tuic = { relay = { - server = server.server_port and server.server .. ":" .. server.server_port, + server = server.server_port and format_host_port(server.server, server.server_port), ip = server.tuic_ip, uuid = server.tuic_uuid, password = server.tuic_passwd, diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/genred2config.sh b/luci-app-ssr-plus/root/usr/share/shadowsocksr/genred2config.sh deleted file mode 100755 index 3750d3f9c..000000000 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/genred2config.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/sh -argv1=$1 -argv2=$2 -argv3=$3 -argv4=$4 -argv5=$5 -argv6=$6 -argv7=$7 -argv8=$8 -argv9=$9 -cat <<-EOF >$argv1 - base { - log_debug = off; - log_info = off; - log = stderr; - daemon = on; - redirector = iptables; - reuseport = on; - } -EOF -tcp() { - if [ "$argv7" == "0" ]; then - cat <<-EOF >>$argv1 - redsocks { - bind = "0.0.0.0:$argv4"; - relay = "$argv5:$argv6"; - type = socks5; - autoproxy = 0; - timeout = 10; - } - EOF - else - cat <<-EOF >>$argv1 - redsocks { - bind = "0.0.0.0:$argv4"; - relay = "$argv5:$argv6"; - type = socks5; - autoproxy = 0; - timeout = 10; - login = "$argv8"; - password = "$argv9"; - } - EOF - fi -} -udp() { - if [ "$argv7" == "0" ]; then - cat <<-EOF >>$argv1 - redudp { - bind = "0.0.0.0:$argv4"; - relay = "$argv5:$argv6"; - type = socks5; - udp_timeout = 10; - } - EOF - else - cat <<-EOF >>$argv1 - redudp { - bind = "0.0.0.0:$argv4"; - relay = "$argv5:$argv6"; - type = socks5; - udp_timeout = 10; - login = "$argv8"; - password = "$argv9"; - } - EOF - fi -} -case "$argv2" in -socks5) - case "$argv3" in - tcp) - tcp - ;; - udp) - udp - ;; - *) - tcp - udp - ;; - esac - ;; -*) - cat <<-EOF >>$argv1 - redsocks { - bind = "0.0.0.0:$argv4"; - type = direct; - interface = $argv3; - autoproxy = 0; - timeout = 10; - } - EOF - ;; -esac diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh index f4ade91b0..fa1688260 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh @@ -18,38 +18,12 @@ case "$USE_TABLES" in exit 1 ;; esac - -netflix() { - local port="$1" - if [ -f "$TMP_DNSMASQ_PATH/gfw_list.conf" ] && [ -s /etc/ssrplus/netflix.list ]; then - grep -vE '^\s*#|^\s*$' /etc/ssrplus/netflix.list > /tmp/ssrplus_netflix.list.clean - if [ -s /tmp/ssrplus_netflix.list.clean ]; then - grep -v -F -f /tmp/ssrplus_netflix.list.clean "$TMP_DNSMASQ_PATH/gfw_list.conf" > "$TMP_DNSMASQ_PATH/gfw_list.conf.tmp" - mv "$TMP_DNSMASQ_PATH/gfw_list.conf.tmp" "$TMP_DNSMASQ_PATH/gfw_list.conf" - if [ -f "$TMP_DNSMASQ_PATH/gfw_base.conf" ]; then - grep -v -F -f /tmp/ssrplus_netflix.list.clean "$TMP_DNSMASQ_PATH/gfw_base.conf" > "$TMP_DNSMASQ_PATH/gfw_base.conf.tmp" - mv "$TMP_DNSMASQ_PATH/gfw_base.conf.tmp" "$TMP_DNSMASQ_PATH/gfw_base.conf" - fi - fi - rm -f /tmp/ssrplus_netflix.list.clean - fi - if [ "$nft_support" = "1" ]; then - # 移除 ipset - cat /etc/ssrplus/netflix.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$port\nnftset=\/&\/inet#ss_spec#netflix/" >$TMP_DNSMASQ_PATH/netflix_forward.conf - elif [ "$nft_support" = "0" ]; then - cat /etc/ssrplus/netflix.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$port\nipset=\/&\/netflix/" >$TMP_DNSMASQ_PATH/netflix_forward.conf - fi -} mkdir -p $TMP_DNSMASQ_PATH -run_mode=$(uci_get_by_type global run_mode router) +run_mode=$(normalize_run_mode) -if [ "$run_mode" = "oversea" ]; then - cp -rf /etc/ssrplus/oversea_list.conf $TMP_DNSMASQ_PATH/ -else - cp -rf /etc/ssrplus/gfw_list.conf $TMP_DNSMASQ_PATH/ - cp -rf /etc/ssrplus/gfw_base.conf $TMP_DNSMASQ_PATH/ -fi +cp -rf /etc/ssrplus/gfw_list.conf $TMP_DNSMASQ_PATH/ +cp -rf /etc/ssrplus/gfw_base.conf $TMP_DNSMASQ_PATH/ for conf_file in gfw_base.conf gfw_list.conf; do conf="$TMP_DNSMASQ_PATH/$conf_file" @@ -67,36 +41,41 @@ for conf_file in gfw_base.conf gfw_list.conf; do fi done -if [ "$(uci_get_by_type global netflix_enable 0)" == "1" ]; then - # 只有开启 NetFlix分流 才需要取值 - SHUNT_SERVER=$(uci_get_by_type global netflix_server nil) -else - # 没有开启 设置为 nil - SHUNT_SERVER=nil -fi -case "$SHUNT_SERVER" in -nil) - rm -f $TMP_DNSMASQ_PATH/netflix_forward.conf - ;; -$(uci_get_by_type global global_server nil) | $switch_server | same) - netflix $dns_port - ;; -*) - netflix $tmp_shunt_dns_port - ;; -esac - # 此处使用 for 方式读取 防止 /etc/ssrplus/ 目录下的 black.list white.list deny.list 等2个或多个文件一行中存在空格 比如:# abc.com 而丢失:server # Optimize: Batch filter using grep for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/deny.list; do if [ -s "$list_file" ]; then - grep -vE '^\s*#|^\s*$' "$list_file" > "${list_file}.clean" + # 清理注释和空行 + grep -vE '^\s*#|^\s*$' "$list_file" | sed 's/\r//g' > "${list_file}.clean" if [ -s "${list_file}.clean" ]; then for target_file in "$TMP_DNSMASQ_PATH/gfw_list.conf" "$TMP_DNSMASQ_PATH/gfw_base.conf"; do - if [ -f "$target_file" ]; then - grep -v -F -f "${list_file}.clean" "$target_file" > "${target_file}.tmp" - mv "${target_file}.tmp" "$target_file" - fi + [ -f "$target_file" ] || continue + tmp_file="${target_file}.tmp" + awk -v list="${list_file}.clean" ' + BEGIN { + while ((getline line < list) > 0) { + gsub(/\r/, "", line) + if (line != "") { + domain[line] = 1 + # 同时支持 *.domain + domain["*." line] = 1 + } + } + close(list) + } + { + # 提取 server=/domain/xxx + if (match($0, /^server=\/([^\/]+)\//, m)) { + if (m[1] in domain) next + } + # 提取 ipset=/domain/xxx + if (match($0, /^ipset=\/([^\/]+)\//, m)) { + if (m[1] in domain) next + } + print + } + ' "$target_file" > "$tmp_file" + mv "$tmp_file" "$target_file" done fi rm -f "${list_file}.clean" @@ -114,19 +93,45 @@ fi cat /etc/ssrplus/deny.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/address=\/&\//" >$TMP_DNSMASQ_PATH/denylist.conf if [ "$(uci_get_by_type global adblock 0)" == "1" ]; then - cp -f /etc/ssrplus/ad.conf $TMP_DNSMASQ_PATH/ + cp -f /etc/ssrplus/ad.conf "$TMP_DNSMASQ_PATH/" if [ -f "$TMP_DNSMASQ_PATH/ad.conf" ]; then - for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/deny.list /etc/ssrplus/netflix.list; do + for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/deny.list; do if [ -s "$list_file" ]; then - grep -vE '^\s*#|^\s*$' "$list_file" > "${list_file}.clean" + # 清理注释 & 空行 + grep -vE '^\s*#|^\s*$' "$list_file" | sed 's/\r//g' > "${list_file}.clean" if [ -s "${list_file}.clean" ]; then - grep -v -F -f "${list_file}.clean" "$TMP_DNSMASQ_PATH/ad.conf" > "$TMP_DNSMASQ_PATH/ad.conf.tmp" - mv "$TMP_DNSMASQ_PATH/ad.conf.tmp" "$TMP_DNSMASQ_PATH/ad.conf" + tmp_file="$TMP_DNSMASQ_PATH/ad.conf.tmp" + awk -v list="${list_file}.clean" ' + BEGIN { + while ((getline line < list) > 0) { + gsub(/\r/, "", line) + if (line != "") { + domain[line] = 1 + # 支持泛域名 + domain["*." line] = 1 + } + } + close(list) + } + { + keep = 1 + # 精确匹配 server=/domain/ + if (match($0, /^server=\/([^\/]+)\//, m)) { + if (m[1] in domain) keep = 0 + } + # 精确匹配 ipset=/domain/ + if (match($0, /^ipset=\/([^\/]+)\//, m)) { + if (m[1] in domain) keep = 0 + } + if (keep) print + } + ' "$TMP_DNSMASQ_PATH/ad.conf" > "$tmp_file" + mv "$tmp_file" "$TMP_DNSMASQ_PATH/ad.conf" fi rm -f "${list_file}.clean" fi done fi else - rm -f $TMP_DNSMASQ_PATH/ad.conf + rm -f "$TMP_DNSMASQ_PATH/ad.conf" fi diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/hy2_test.sh b/luci-app-ssr-plus/root/usr/share/shadowsocksr/hy2_test.sh deleted file mode 100755 index 1bac264dc..000000000 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/hy2_test.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/sh -# /usr/share/shadowsocksr/test.sh - -CONFIG="shadowsocksr" -LOCK_PATH=/tmp/lock -TMP_PATH=/var/etc/ssrplus - -uci_get_by_name() { - local ret=$(uci -q get $CONFIG.$1.$2 2>/dev/null) - echo "${ret:=$3}" -} - -uci_get_by_type() { - local ret=$(uci -q get $CONFIG.@$1[0].$2 2>/dev/null) - echo "${ret:=$3}" -} - -check_port_exists() { - local port=$1 - local protocol=$2 - [ -n "$protocol" ] || protocol="tcp,udp" - local result= - if [ "$protocol" = "tcp" ]; then - result=$(netstat -tln | grep -c ":$port ") - elif [ "$protocol" = "udp" ]; then - result=$(netstat -uln | grep -c ":$port ") - elif [ "$protocol" = "tcp,udp" ]; then - result=$(netstat -tuln | grep -c ":$port ") - fi - echo "${result}" -} - -set_cache_var() { - local key="${1}" - shift 1 - local val="$@" - [ -n "${key}" ] && [ -n "${val}" ] && { - sed -i "/${key}=/d" $TMP_PATH/var >/dev/null 2>&1 - echo "${key}=\"${val}\"" >> $TMP_PATH/var - eval ${key}=\"${val}\" - } -} - -get_cache_var() { - local key="${1}" - [ -n "${key}" ] && [ -s "$TMP_PATH/var" ] && { - echo $(cat $TMP_PATH/var | grep "^${key}=" | awk -F '=' '{print $2}' | tail -n 1 | awk -F'"' '{print $2}') - } -} - -#uci_get_by_port() { -# local port=$1 -# while netstat -tuln 2>/dev/null | grep -q ":${port} "; do -# port=$((port + 1)) -# done -# echo $port - -uci_get_by_port() { - local default_start_port=2001 - local min_port=1025 - local max_port=49151 - local port="$1" - local protocol=$(echo "$2" | tr 'A-Z' 'a-z') - local LOCK_FILE="${LOCK_PATH}/${CONFIG}_get_prot.lock" - while ! mkdir "$LOCK_FILE" 2>/dev/null; do - sleep 1 - done - if [ "$port" = "auto" ]; then - local now last_time diff last_port - now=$(date +%s 2>/dev/null) - last_time=$(get_cache_var "last_get_new_port_time") - if [ -n "$now" ] && [ -n "$last_time" ]; then - diff=$(expr "$now" - "$last_time") - [ "$diff" -lt 0 ] && diff=$(expr 0 - "$diff") - else - diff=999 - fi - if [ "$diff" -gt 10 ]; then - port=$default_start_port - else - last_port=$(get_cache_var "last_get_new_port_auto") - if [ -n "$last_port" ]; then - port=$(expr "$last_port" + 1) - else - port=$default_start_port - fi - fi - fi - [ "$port" -lt $min_port -o "$port" -gt $max_port ] && port=$default_start_port - local start_port="$port" - while :; do - if [ "$(check_port_exists "$port" "$protocol")" = 0 ]; then - break - fi - port=$(expr "$port" + 1) - if [ "$port" -gt $max_port ]; then - port=$min_port - fi - [ "$port" = "$start_port" ] && { - rmdir "$LOCK_FILE" 2>/dev/null - return 1 - } - done - if [ "$1" = "auto" ]; then - set_cache_var "last_get_new_port_auto" "$port" - [ -n "$now" ] && set_cache_var "last_get_new_port_time" "$now" - fi - rmdir "$LOCK_FILE" 2>/dev/null - echo "$port" -} - -url_test_hy2() { - local node_id=$1 - - # 读取配置 - local server=$(uci_get_by_name ${node_id} server) - local port=$(uci_get_by_name ${node_id} server_port) - local auth=$(uci_get_by_name ${node_id} hy2_auth) - local tls=$(uci_get_by_name ${node_id} tls) - local insecure=$(uci_get_by_name ${node_id} insecure) - local tls_host=$(uci_get_by_name ${node_id} tls_host) - - # 获取本地端口 - # local tmp_port=$(uci_get_by_port 48900 tcp,udp) - local tmp_port=$(uci_get_by_port auto tcp,udp) - - # 生成Hysteria2配置文件 - local config_file="/tmp/hy2_test_${node_id}.yaml" - cat > "$config_file" <<-EOF - server: ${server}:${port} - auth: "${auth}" - tls: - insecure: true - EOF - - # 如果 tls_host 非空,动态添加 sni 行 - [ -n "$tls_host" ] && echo " sni: \"${tls_host}\"" >> "$config_file" - - # 追加 socks5 监听配置 - cat >> "$config_file" <<-EOF - socks5: - listen: 127.0.0.1:${tmp_port} - EOF - - # echo "Debug: 配置文件已生成: $config_file" >&2 - - # 启动Hysteria2客户端 - hysteria client --disable-update-check -c "$config_file" >/dev/null 2>&1 & - local pid=$! - echo $pid > "/tmp/hy2_test_${node_id}.pid" - - # 等待端口启动 - sleep 1 - - # 测试代理 - # local result=$(curl --connect-timeout 3 --max-time 3 -s -o /dev/null -I -w "%{http_code}:%{time_pretransfer}" --socks5 127.0.0.1:${tmp_port} "${probeUrl}" 2>/dev/null) - local curlx="socks5h://127.0.0.1:${tmp_port}" - local probeUrl=$(uci_get_by_type server_subscribe url_test_url https://www.google.com/generate_204) - local result=$(curl --connect-timeout 3 --max-time 5 -o /dev/null -I -skL -w "%{http_code}:%{time_pretransfer}" -x ${curlx} "${probeUrl}" 2>/dev/null) - - # 清理 - # kill -9 $pid 2>/dev/null - local pid_file="/tmp/hy2_test_${node_id}.pid" - [ -s "$pid_file" ] && kill -9 "$(head -n 1 "$pid_file")" >/dev/null 2>&1 - pgrep -af "hysteria.*${config_file}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1 - rm -f "$config_file" "$pid_file" - - echo $result -} - -case $1 in - url_test_hy2) - url_test_hy2 $2 - ;; -esac \ No newline at end of file diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config b/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config index ded6977f3..6d1d5ac13 100644 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config @@ -1,35 +1,35 @@ config global option global_server 'nil' - option netflix_server 'nil' - option netflix_proxy '0' option threads '0' option run_mode 'router' option dports '2' option custom_ports '80,443' option pdnsd_enable '1' - option prefer_nft '1' + option filter_aaaa '1' option tunnel_forward '8.8.4.4:53' option monitor_enable '1' option enable_switch '1' option switch_time '667' option switch_timeout '5' option switch_try_count '3' - option shunt_dns '1' + option default_node_local_port '1234' + option component_mirror 'direct' option gfwlist_url 'https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt' option chnroute_url 'https://ispip.clang.cn/all_cn.txt' - option nfip_url 'https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt' option adblock_url 'https://anti-ad.net/anti-ad-for-dnsmasq.conf' config server_subscribe - option proxy '0' + option proxy '1' option auto_update '1' + option config_auto_update_mode '0' + option config_update_interval '60' option auto_update_week_time '*' option auto_update_day_time '2' option auto_update_min_time '0' option url_test_url 'https://www.google.com/generate_204' option user_agent 'v2rayN/9.99' - option filter_words '过期/套餐/剩余/QQ群/官网/防失联/回国' + option filter_words '过期/套餐/剩余/网址/QQ群/官网/防失联/回国' config access_control option lan_ac_mode '0' @@ -47,6 +47,11 @@ config socks5_proxy option server 'nil' option local_port '1080' +config http_proxy + option server 'nil' + option local_port '3128' + option http_auth 'none' + config server_global option enable_server '0' @@ -54,3 +59,9 @@ config global_xray_fragment option fragment '0' option noise '0' +config clash_client_group + option enabled '0' + option remarks '' + option ip_addr '' + option client_mac '' + option policy_group '' diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/ssrplusupdate.sh b/luci-app-ssr-plus/root/usr/share/shadowsocksr/ssrplusupdate.sh index 9424cfac7..9c69b511e 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/ssrplusupdate.sh +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/ssrplusupdate.sh @@ -1,6 +1,43 @@ #!/bin/sh +LOCK_DIR="/var/run/ssrplusupdate.lock" +LOOP_STAMP="/var/run/ssrplusupdate.loop" +MODE="$(uci -q get shadowsocksr.@server_subscribe[0].config_auto_update_mode 2>/dev/null || echo 0)" + +mkdir "$LOCK_DIR" 2>/dev/null || exit 0 +trap 'rmdir "$LOCK_DIR" 2>/dev/null' EXIT INT TERM + +if [ "$1" = "loop" ]; then + [ "$(uci -q get shadowsocksr.@server_subscribe[0].auto_update 2>/dev/null || echo 0)" = "1" ] || exit 0 + [ "$MODE" = "1" ] || exit 0 + + INTERVAL="$(uci -q get shadowsocksr.@server_subscribe[0].config_update_interval 2>/dev/null || echo 60)" + case "$INTERVAL" in + ''|*[!0-9]*) + INTERVAL=60 + ;; + esac + [ "$INTERVAL" -gt 0 ] 2>/dev/null || INTERVAL=60 + + NOW="$(date +%s)" + LAST_RUN=0 + [ -f "$LOOP_STAMP" ] && LAST_RUN="$(cat "$LOOP_STAMP" 2>/dev/null || echo 0)" + case "$LAST_RUN" in + ''|*[!0-9]*) + LAST_RUN=0 + ;; + esac + + if [ $((NOW - LAST_RUN)) -lt $((INTERVAL * 60)) ]; then + exit 0 + fi +fi + /usr/bin/lua /usr/share/shadowsocksr/update.lua sleep 2s /usr/share/shadowsocksr/chinaipset.sh /var/etc/ssrplus/china_ssr.txt sleep 2s /usr/bin/lua /usr/share/shadowsocksr/subscribe.lua + +if [ "$1" = "loop" ]; then + date +%s > "$LOOP_STAMP" +fi diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua index 251ad828d..05acd8b7d 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua @@ -23,35 +23,37 @@ local nodeResult = setmetatable({}, {__index = cache}) -- update result local name = 'shadowsocksr' local uciType = 'servers' local ucic = require "luci.model.uci".cursor() -local proxy = ucic:get_first(name, 'server_subscribe', 'proxy') or '0' -local switch = ucic:get_first(name, 'server_subscribe', 'switch') or '1' -local allow_insecure = ucic:get_first(name, 'server_subscribe', 'allow_insecure') or '0' -local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url') or {} -local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words') or '过期时间/剩余流量' -local save_words = ucic:get_first(name, 'server_subscribe', 'save_words') or '' -local user_agent = ucic:get_first(name, 'server_subscribe', 'user_agent') or 'v2rayN/9.99' -local domain_resolver = ucic:get_first(name, 'server_subscribe', 'domain_resolver') or '' -local domain_resolver_dns = ucic:get_first(name, 'server_subscribe', 'domain_resolver_dns') or '' -local domain_resolver_dns_https = ucic:get_first(name, 'server_subscribe', 'domain_resolver_dns_https') or '' -local domain_strategy = ucic:get_first(name, 'server_subscribe', 'domain_strategy') or '' - --- 读取 ss_type 设置 -local ss_type = ucic:get_first(name, 'server_subscribe', 'ss_type') or '' --- 读取 xray_hy2_type 设置 -local xray_hy2_type = ucic:get_first(name, 'server_subscribe', 'xray_hy2_type') or '' --- 读取 xray_tj_type 设置 -local xray_tj_type = ucic:get_first(name, 'server_subscribe', 'xray_tj_type') or '' +local proxy = ucic:get_first(name, 'server_subscribe', 'proxy', '0') +local switch = ucic:get_first(name, 'server_subscribe', 'switch', '1') +local allow_insecure = ucic:get_first(name, 'server_subscribe', 'allow_insecure', '0') +local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期/套餐/剩余/网址/QQ群/官网/防失联/回国') +local save_words = ucic:get_first(name, 'server_subscribe', 'save_words', '') +local user_agent = ucic:get_first(name, 'server_subscribe', 'user_agent', 'v2rayN/9.99') +local local_clash_dir = "/etc/ssrplus/clash" +local target_subscribe_sid = tostring(arg and arg[1] or ""):gsub("^%s*(.-)%s*$", "%1") local has_ss_rust = luci.sys.exec('type -t -p sslocal 2>/dev/null || type -t -p ssserver 2>/dev/null') ~= "" -local has_ss_libev = luci.sys.exec('type -t -p ss-redir 2>/dev/null || type -t -p ss-local 2>/dev/null') ~= "" -local has_hysteria = luci.sys.exec('type -t -p hysteria 2>/dev/null') ~= "" -local has_trojan = luci.sys.exec('type -t -p trojan 2>/dev/null') ~= "" local has_xray = luci.sys.exec('type -t -p xray 2>/dev/null') ~= "" +local has_mihomo = luci.sys.exec('type -t -p mihomo -p /usr/libexec/mihomo 2>/dev/null') ~= "" -local tuic_type = luci.sys.exec('type -t -p tuic-client') ~= "" and "tuic" +local tuic_type = luci.sys.exec('type -t -p mihomo -p /usr/libexec/mihomo 2>/dev/null') ~= "" and "tuic" local log = function(...) print(os.date("%Y-%m-%d %H:%M:%S ") .. table.concat({...}, " ")) end + +local function preferred_ss_backend() + if has_mihomo then + return "ss" + end + if has_ss_rust then + return "ss-rust" + end + if has_xray then + return "v2ray" + end + return nil +end + local encrypt_methods_ss = { -- plain "none", @@ -137,6 +139,128 @@ local function trim(text) end return (sgsub(text, "^%s*(.-)%s*$", "%1")) end + +local function shell_quote(value) + value = tostring(value or "") + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +local function nft_string_literal(value) + value = tostring(value or "") + value = value:gsub("\\", "\\\\"):gsub('"', '\\"') + return '"' .. value .. '"' +end + +local function escape_lua_pattern(value) + return tostring(value or ""):gsub("([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1") +end + +local function is_true_value(v) + if v == nil then + return false + end + if v == true then + return true + end + if type(v) == "string" then + local s = trim(string.lower(v)) + return s == "1" or s == "true" or s == "yes" or s == "on" + end + return false +end + +local function collect_subscribe_items() + local items = {} + + ucic:foreach(name, "server_subscribe_item", function(s) + if target_subscribe_sid ~= "" and s[".name"] ~= target_subscribe_sid then + return + end + + local url = trim(s.url or "") + if url == "" then + return + end + + if target_subscribe_sid == "" and not is_true_value(s.enabled or "1") then + return + end + + items[#items + 1] = { + sid = s[".name"], + alias = trim(s.alias or ""), + url = url + } + end) + + if #items > 0 then + return items + end + + if target_subscribe_sid ~= "" then + return items + end + + local legacy_urls = ucic:get_first(name, 'server_subscribe', 'subscribe_url', {}) + for index, url in ipairs(legacy_urls or {}) do + url = trim(url) + if url ~= "" then + items[#items + 1] = { + sid = "legacy_" .. index, + alias = "Legacy " .. index, + url = url + } + end + end + + return items +end + +local subscribe_items = collect_subscribe_items() + +local function first_nonempty(tbl, keys) + for _, key in ipairs(keys) do + local value = tbl[key] + if value ~= nil and value ~= "" then + return value + end + end + return nil +end + +local function normalize_host(value) + value = trim(value or "") + if value == "" then + return value + end + if value:match("^%[.*%]$") then + return value:sub(2, -2) + end + return value +end + +local function parse_host_port(value, default_port) + value = trim(value or "") + if value == "" then + return nil, default_port + end + + local host, port = value:match("^%[(.*)%]:(%d+)$") + if host then + return normalize_host(host), port + end + + host, port = value:match("^(.-):(%d+)$") + if host and host ~= "" and not host:find(":", 1, true) then + return normalize_host(host), port + end + + if value:find(":", 1, true) then + return normalize_host(value), default_port + end + + return normalize_host(value), default_port +end -- md5 local function md5(content) local stdout = luci.sys.exec('echo \"' .. urlEncode(content) .. '\" | md5sum | cut -d \" \" -f1') @@ -196,9 +320,174 @@ local function isCompleteJSON(str) local success, _ = pcall(jsonParse, str) return success end + +local function isClashYAML(str) + if type(str) ~= "string" or str:match("^%s*$") then + return false + end + + for line in str:gmatch("[^\r\n]+") do + if line:match("^%s*proxies%s*:") or line:match("^%s*proxy%-providers%s*:") then + return true + end + end + + return false +end + +local function processClashSubscription(url) + local ok, parsed = pcall(URL.parse, url) + if not ok or not parsed or not parsed.host then + return nil + end + + local alias = "Clash_" .. parsed.host + local server_port = parsed.port or ((parsed.scheme == "http") and "80" or "443") + local result = { + type = "clash", + server = normalize_host(parsed.host), + server_port = server_port, + clash_url = url, + clash_user_agent = user_agent, + raw_alias = alias, + alias = alias + } + + local saved_alias = result.alias + result.alias = nil + result.hashkey = md5(jsonStringify(result) .. "_" .. (saved_alias or "")) + result.alias = saved_alias + return result +end + +local function yaml_quote(str) + str = tostring(str or "") + str = str:gsub("\\", "\\\\"):gsub('"', '\\"') + return '"' .. str .. '"' +end + +local function is_ip_literal(value) + if not value or value == "" then + return false + end + + if value:match("^%d+%.%d+%.%d+%.%d+$") then + return true + end + + return value:find(":", 1, true) ~= nil +end + +local function parseAnytlsShare(content) + local alias = "" + if content:find("#", 1, true) then + local idx = content:find("#", 1, true) + alias = UrlDecode(content:sub(idx + 1)) + content = content:sub(1, idx - 1) + end + + local main, query = content, "" + if content:find("%?", 1) then + local idx = content:find("%?", 1) + main = content:sub(1, idx - 1) + query = content:sub(idx + 1) + end + + local userinfo, hostinfo = main:match("^([^@]+)@(.+)$") + if not userinfo or not hostinfo then + return nil + end + + local password = UrlDecode(userinfo) + local server, port = hostinfo:match("^(.+):(%d+)$") + if not server or not port then + server = hostinfo + port = "443" + end + server = server:gsub("^%[", ""):gsub("%]$", "") + + local params = {} + for _, v in ipairs(split(query, "&")) do + local t = split(v, "=") + if #t > 1 then + params[string.lower(t[1])] = UrlDecode(t[2] or "") + end + end + + return { + name = (alias ~= "" and alias) or (server .. ":" .. port), + server = server, + port = tonumber(port), + password = password, + sni = (function() + local sni = params.sni or params.servername or "" + if is_ip_literal(sni) then + return "" + end + return sni + end)(), + allow_insecure = (params.insecure == "1" or params.allow_insecure == "1" or params.allowinsecure == "1"), + client_fingerprint = params.fp or params.fingerprint or "" + } +end + +local function buildAnytlsClashYaml(entries, group_name) + local lines = { + "mode: rule", + "log-level: silent", + "proxies:" + } + + for _, node in ipairs(entries) do + lines[#lines + 1] = " - name: " .. yaml_quote(node.name) + lines[#lines + 1] = " type: anytls" + lines[#lines + 1] = " server: " .. yaml_quote(node.server) + lines[#lines + 1] = " port: " .. tostring(node.port) + lines[#lines + 1] = " password: " .. yaml_quote(node.password) + if node.sni and node.sni ~= "" then + lines[#lines + 1] = " sni: " .. yaml_quote(node.sni) + end + if node.allow_insecure then + lines[#lines + 1] = " skip-cert-verify: true" + end + if node.client_fingerprint and node.client_fingerprint ~= "" then + lines[#lines + 1] = " client-fingerprint: " .. yaml_quote(node.client_fingerprint) + end + end + + lines[#lines + 1] = "proxy-groups:" + lines[#lines + 1] = " - name: " .. yaml_quote(group_name) + lines[#lines + 1] = " type: select" + lines[#lines + 1] = " proxies:" + for _, node in ipairs(entries) do + lines[#lines + 1] = " - " .. yaml_quote(node.name) + end + lines[#lines + 1] = "rules:" + lines[#lines + 1] = " - MATCH," .. group_name + + return table.concat(lines, "\n") .. "\n" +end + +local function processLocalClashSubscription(path, alias) + local result = { + type = "clash", + server = "127.0.0.1", + server_port = "0", + clash_path = path, + clash_user_agent = user_agent, + raw_alias = alias, + alias = alias + } + + local saved_alias = result.alias + result.alias = nil + result.hashkey = md5(jsonStringify(result) .. "_" .. (saved_alias or "")) + result.alias = saved_alias + return result +end -- 处理数据 local function processData(szType, content, cfgid) - local result = {type = szType, local_port = 1234, kcp_param = '--nocomp'} + local result = {type = szType, kcp_param = '--nocomp'} -- 检查JSON的格式如不完整丢弃 if not (szType == "sip008" or szType == "ssd") then if not isCompleteJSON(content) then @@ -210,115 +499,45 @@ local function processData(szType, content, cfgid) local url = URL.parse("http://" .. content) local params = url.query - -- 调试输出所有参数 - -- log("Hysteria2 原始参数:") - -- for k,v in pairs(params) do - -- log(k.."="..v) - -- end - - -- 自动决定模式(true=Xray, false=普通) - local xray_hy2_mode = false -- 默认普通模式 - if xray_hy2_type == "v2ray" then - -- Xray 模式 - if has_xray then - xray_hy2_mode = true - elseif has_hysteria then - xray_hy2_mode = false -- 回退到普通 Hysteria2 - else - xray_hy2_mode = nil - end - elseif xray_hy2_type == "hysteria2" then - -- 普通 Hysteria2 模式 - if has_hysteria then - xray_hy2_mode = false - elseif has_xray then - xray_hy2_mode = true -- 回退到 Xray - else - xray_hy2_mode = nil - end - else - -- auto 或空:优先普通 Hysteria2,若不存在则使用 Xray - if has_hysteria then - xray_hy2_mode = false - elseif has_xray then - xray_hy2_mode = true -- 回退到 Xray - else - xray_hy2_mode = nil - end - end - - -- 如果无法确定模式,跳过该订阅 - if xray_hy2_mode == nil then + if not has_xray then return nil end - - if xray_hy2_mode then - result.type = "v2ray" - result.v2ray_protocol = "hysteria2" - if params.fm and params.fm ~= "" then - result.enable_finalmask = "1" - result.finalmask = base64Encode(params.fm) + + result.type = "v2ray" + result.v2ray_protocol = "hysteria2" + if params.fm and params.fm ~= "" then + result.enable_finalmask = "1" + result.finalmask = base64Encode(params.fm) + end + if (params.security and params.security:lower() == "tls") + or (params.sni and params.sni ~= "") + or (params.alpn and params.alpn ~= "") + or (params.pcs or params.vcn) then + result.tls = "1" + if params.sni then + result.tls_host = params.sni end - if (params.security and params.security:lower() == "tls") - or (params.sni and params.sni ~= "") - or (params.alpn and params.alpn ~= "") - or (params.pcs or params.vcn) then - result.tls = "1" - if params.sni then - result.tls_host = params.sni + if params.alpn and params.alpn ~= "" then + local alpn = {} + for v in params.alpn:gmatch("[^,;|%s]+") do + table.insert(alpn, v) end - if params.alpn and params.alpn ~= "" then - local alpn = {} - for v in params.alpn:gmatch("[^,;|%s]+") do - table.insert(alpn, v) - end - if #alpn > 0 then - result.tls_alpn = table.concat(alpn, ",") -- 确保为字符串 - end - end - if params.pcs then - result.tls_CertSha = params.pcs - end - if params.vcn then - result.tls_CertByName = params.vcn + if #alpn > 0 then + result.tls_alpn = table.concat(alpn, ",") end end - else - result.type = "hysteria2" - if params.protocol and params.protocol ~= "" then - result.flag_transport = "1" - result.transport_protocol = params.protocol - else - result.flag_transport = "1" - result.transport_protocol = "udp" + if params.pcs then + result.tls_CertSha = params.pcs end - if params.lazy and params.lazy ~= "" then - result.lazy_mode = "1" - end - if (params.sni and params.sni ~= "") or (params.alpn and params.alpn ~= "") then - result.tls = "1" - if params.sni then - result.tls_host = params.sni - end - if params.alpn and params.alpn ~= "" then - local alpn = {} - for v in params.alpn:gmatch("[^,;|%s]+") do - table.insert(alpn, v) - end - if #alpn > 0 then - result.tls_alpn = table.concat(alpn, ",") -- 确保为字符串 - end - end - end - if params.pinSHA256 and params.pinSHA256 ~= "" then - result.pinsha256 = params.pinSHA256 + if params.vcn then + result.tls_CertByName = params.vcn end end local raw_alias = url.fragment and UrlDecode(url.fragment) or nil result.raw_alias = raw_alias -- 新增 result.alias = raw_alias -- 临时赋值(后面会被覆盖) - result.server = url.host + result.server = normalize_host(url.host) result.server_port = url.port or 443 result.hy2_auth = url.user @@ -347,15 +566,22 @@ local function processData(szType, content, cfgid) -- 去掉前后空白和#注释 local link = trim(content:gsub("#.*$", "")) local dat = split(link, "/%?") - local hostInfo = split(dat[1] or '', ':') + local host, port, rest + local hostinfo = dat[1] or '' + if hostinfo:find("^%[.*%]:") then + host, port, rest = hostinfo:match("^%[(.*)%]:(%d+):(.*)$") + else + host, port, rest = hostinfo:match("^(.-):(%d+):(.*)$") + end result.type = 'ssr' - result.server = hostInfo[1] or '' - result.server_port = hostInfo[2] or '' - result.protocol = hostInfo[3] or '' - result.encrypt_method = hostInfo[4] or '' - result.obfs = hostInfo[5] or '' - result.password = base64Decode(hostInfo[6] or '') + local ssr_parts = split(rest or '', ':') + result.server = normalize_host(host or '') + result.server_port = port or '' + result.protocol = ssr_parts[1] or '' + result.encrypt_method = ssr_parts[2] or '' + result.obfs = ssr_parts[3] or '' + result.password = base64Decode(ssr_parts[4] or '') local params = {} if dat[2] and dat[2] ~= '' then @@ -379,7 +605,7 @@ local function processData(szType, content, cfgid) local remarks = base64Decode(params.remarks or '') -- 拼接 alias - local alias = "" + local raw_alias = "" if group ~= "" then raw_alias = "[" .. group .. "] " end @@ -556,60 +782,11 @@ local function processData(szType, content, cfgid) result.fast_open = params.tfo end - -- 自动决定模式(true=Xray, false=普通 SS) - local xray_ss_mode = false - if ss_type == "v2ray" then - -- Xray 模式 - if has_xray then - xray_ss_mode = true - elseif has_ss_rust or has_ss_libev then - xray_ss_mode = false -- 回退到普通 SS - else - xray_ss_mode = nil - end - elseif ss_type == "ss-rust" or ss_type == "ss-libev" then - -- 普通 SS 模式 - local user_core = (ss_type == "ss-rust" and has_ss_rust) or (ss_type == "ss-libev" and has_ss_libev) - if user_core then - xray_ss_mode = false -- 否则普通 SS - else - -- 指定的核心不存在,尝试另一个 SS 核心 - local other_core = (ss_type == "ss-rust" and has_ss_libev) or (ss_type == "ss-libev" and has_ss_rust) - if other_core then - xray_ss_mode = false -- 使用存在的另一个 SS 核心 - elseif has_xray then - xray_ss_mode = true -- 回退到 Xray - else - xray_ss_mode = nil - end - end - else - -- ss_type 为空或 auto:根据链接中是否有 type 参数决定 - local has_type = params.type and params.type ~= "" - if has_type then - -- 有 type 参数,优先 Xray - if has_xray then - xray_ss_mode = true - elseif has_ss_rust or has_ss_libev then - xray_ss_mode = false -- 回退到普通 SS - else - xray_ss_mode = nil - end - else - -- 无 type 参数,优先普通 SS - if has_ss_rust or has_ss_libev then - -- 普通 SS 模式 - xray_ss_mode = false - elseif has_xray then - xray_ss_mode = true -- 回退到 Xray - else - xray_ss_mode = nil - end - end - end + local selected_ss_backend = preferred_ss_backend() + local xray_ss_mode = (selected_ss_backend == "v2ray") -- 如果最终无可用核心,跳过该订阅 - if xray_ss_mode == nil then + if selected_ss_backend == nil then return nil end @@ -619,7 +796,7 @@ local function processData(szType, content, cfgid) result.type = "v2ray" result.v2ray_protocol = "shadowsocks" - result.server = url.host + result.server = normalize_host(url.host) result.server_port = url.port -- 判断 @ 前部分是否为 Base64 @@ -676,7 +853,7 @@ local function processData(szType, content, cfgid) -- 检查 finalmaskg 参数是否存在且非空 if params.fm and params.fm ~= "" then result.enable_finalmask = "1" - result.finalmaskg = base64Encode(params.fm) + result.finalmask = base64Encode(params.fm) end -- 检查 pqv 参数是否存在且非空 if params.pqv and params.pqv ~= "" then @@ -806,13 +983,7 @@ local function processData(szType, content, cfgid) end -- 填充 result - local xray_ss_type - if ss_type == "ss-rust" or ss_type == "ss-libev" then - xray_ss_type = ss_type - else - xray_ss_type = has_ss_rust and "ss-rust" or "ss-libev" - end - result.type = xray_ss_type + result.type = selected_ss_backend result.encrypt_method_ss = method result.password = password result.server = server @@ -837,7 +1008,7 @@ local function processData(szType, content, cfgid) if result.plugin ~= "none" and result.plugin ~= "" then result.enable_plugin = 1 end - elseif has_ss_type and has_ss_type ~= "ss-libev" then + else if params["shadow-tls"] then -- 特别处理 shadow-tls 作为插件 -- log("原始 shadow-tls 参数:", params["shadow-tls"]) @@ -863,11 +1034,6 @@ local function processData(szType, content, cfgid) end end end - else - if params["shadow-tls"] then - log("错误:ShadowSocks-libev 不支持使用 shadow-tls 插件") - return nil, "ShadowSocks-libev 不支持使用 shadow-tls 插件" - end end -- 检查加密方法是否受支持 @@ -878,12 +1044,13 @@ local function processData(szType, content, cfgid) end end elseif szType == "sip008" then - result.type = v2_ss - if v2_ss ~= "v2ray" then - result.has_ss_type = has_ss_type - else - result.xray_has_ss_type = "v2ray" - result.v2ray_protocol = has_v2_ss_type + local selected_ss_backend = preferred_ss_backend() + if not selected_ss_backend then + return nil + end + result.type = selected_ss_backend + if selected_ss_backend == "v2ray" then + result.v2ray_protocol = "shadowsocks" end result.server = content.server result.server_port = content.server_port @@ -897,12 +1064,13 @@ local function processData(szType, content, cfgid) result.server = nil end elseif szType == "ssd" then - result.type = v2_ss - if v2_ss ~= "v2ray" then - result.has_ss_type = has_ss_type - else - result.xray_has_ss_type = "v2ray" - result.v2ray_protocol = has_v2_ss_type + local selected_ss_backend = preferred_ss_backend() + if not selected_ss_backend then + return nil + end + result.type = selected_ss_backend + if selected_ss_backend == "v2ray" then + result.v2ray_protocol = "shadowsocks" end result.server = content.server result.server_port = content.port @@ -952,13 +1120,7 @@ local function processData(szType, content, cfgid) end -- 提取服务器地址和端口 - if host_port:find(":") then - local sp = split(host_port, ":") - result.server_port = sp[#sp] - result.server = sp[1] - else - result.server = host_port - end + result.server, result.server_port = parse_host_port(host_port, "443") -- 默认设置 -- 按照官方的建议 默认验证ssl证书 @@ -977,14 +1139,23 @@ local function processData(szType, content, cfgid) end end - if params.peer or params.sni then - -- 未指定peer(sni)默认使用remote addr - result.tls_host = params.peer or params.sni + do + local tls_host = first_nonempty(params, {"peer", "sni", "host"}) + if tls_host then + -- 未指定 peer/sni 时,兼容使用 host 作为 TLS Host + result.tls_host = tls_host + end end -- 处理 insecure 参数 - if params.allowInsecure or params.allowinsecure or params.insecure then - local insecure = params.allowInsecure or params.allowinsecure or params.insecure - if insecure == true or insecure == "1" or insecure == "true" then + do + local insecure = first_nonempty(params, { + "allowInsecure", + "allowinsecure", + "allow_insecure", + "insecure", + "skip-cert-verify" + }) + if is_true_value(insecure) then result.insecure = "1" end end @@ -997,136 +1168,88 @@ local function processData(szType, content, cfgid) end -- 自动决定模式(true=Xray, false=普通 Trojan) - local xray_tj_mode = false - if xray_tj_type == "v2ray" then - -- Xray 模式 - if has_xray then - xray_tj_mode = true - elseif has_trojan then - xray_tj_mode = false -- 回退到普通 Trojan - else - xray_tj_mode = nil -- 两类核心均不存在,停止订阅 - end - elseif xray_tj_type == "trojan" then - -- 普通 Trojan 模式 - if has_trojan then - xray_tj_mode = false - elseif has_xray then - xray_tj_mode = true -- 回退到 Xray - else - xray_tj_mode = nil -- 两类核心均不存在,停止订阅 - end - else - -- 全局配置为空或 auto,根据链接中是否有 type 参数决定 - local has_type = params.type and params.type ~= "" - -- 有 type 参数,优先 Xray - if has_type then - if has_xray then - xray_tj_mode = true -- 有 type 参数使用 Xray - elseif has_trojan then - xray_tj_mode = false -- 否则普通 Trojan - else - xray_tj_mode = nil -- 两类核心均不存在,停止订阅 - end - else - -- 无 type 参数,优先普通 Trojan - if has_trojan then - xray_tj_mode = false -- 普通 Trojan - elseif has_xray then - xray_tj_mode = true -- 否则使用 Xray - else - xray_tj_mode = nil -- 两类核心均不存在,停止订阅 - end - end - end - - -- 如果最终无可用核心,跳过该订阅 - if xray_tj_mode == nil then + if not has_xray then return nil end - if xray_tj_mode then - result.type = "v2ray" - result.v2ray_protocol = "trojan" - if params.fp then - -- 处理 fingerprint 参数 - result.fingerprint = params.fp + result.type = "v2ray" + result.v2ray_protocol = "trojan" + if params.fp then + -- 处理 fingerprint 参数 + result.fingerprint = params.fp + end + -- 处理 ech 参数 + if params.ech and params.ech ~= "" then + result.enable_ech = "1" + result.ech_config = params.ech + end + -- 检查 finalmaskg 参数是否存在且非空 + if params.fm and params.fm ~= "" then + result.enable_finalmask = "1" + result.finalmask = base64Encode(params.fm) + end + -- 处理传输协议 + result.transport = params.type or "raw" -- 默认传输协议为 raw + if result.transport == "tcp" then + result.transport = "raw" + end + if result.transport == "splithttp" then + result.transport = "xhttp" + end + if params.pcs and params.pcs ~= "" then + result.tls_CertSha = params.pcs + end + if params.vcn and params.vcn ~= "" then + result.tls_CertByName = params.vcn + end + if result.transport == "ws" then + result.ws_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil + result.ws_path = params.path and UrlDecode(params.path) or "/" + elseif result.transport == "httpupgrade" then + result.httpupgrade_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil + result.httpupgrade_path = params.path and UrlDecode(params.path) or "/" + elseif result.transport == "xhttp" or result.transport == "splithttp" then + result.xhttp_mode = params.mode or "auto" + result.xhttp_host = params.host and UrlDecode(params.host) or nil + result.xhttp_path = params.path and UrlDecode(params.path) or "/" + -- 检查 extra 参数是否存在且非空 + if params.extra and params.extra ~= "" then + result.enable_xhttp_extra = "1" + result.xhttp_extra = base64Encode(params.extra) end - -- 处理 ech 参数 - if params.ech and params.ech ~= "" then - result.enable_ech = "1" - result.ech_config = params.ech + -- 尝试解析 JSON 数据 + local success, Data = pcall(jsonParse, params.extra or "") + if success and type(Data) == "table" then + local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address) + or (Data.downloadSettings and Data.downloadSettings.address) + result.download_address = (address and address ~= "") and address:gsub("^%[", ""):gsub("%]$", "") + else + -- 如果解析失败,清空下载地址 + result.download_address = nil end - -- 检查 finalmaskg 参数是否存在且非空 - if params.fm and params.fm ~= "" then - result.enable_finalmask = "1" - result.finalmaskg = base64Encode(params.fm) + elseif result.transport == "http" or result.transport == "h2" then + result.transport = "h2" + result.h2_host = params.host and UrlDecode(params.host) or nil + result.h2_path = params.path and UrlDecode(params.path) or nil + elseif result.transport == "kcp" then + result.kcp_guise = params.headerType or "none" + if params.headerType and params.headerType == "dns" then + result.kcp_domain = params.host or "" end - -- 处理传输协议 - result.transport = params.type or "raw" -- 默认传输协议为 raw - if result.transport == "tcp" then - result.transport = "raw" + result.seed = params.seed + elseif result.transport == "quic" then + result.quic_guise = params.headerType or "none" + result.quic_security = params.quicSecurity or "none" + result.quic_key = params.key + elseif result.transport == "grpc" then + result.serviceName = params.serviceName + result.grpc_mode = params.mode or "gun" + elseif result.transport == "tcp" or result.transport == "raw" then + result.tcp_guise = params.headerType and params.headerType ~= "" and params.headerType or "none" + if result.tcp_guise == "http" then + result.tcp_host = params.host and UrlDecode(params.host) or nil + result.tcp_path = params.path and UrlDecode(params.path) or nil end - if result.transport == "splithttp" then - result.transport = "xhttp" - end - if params.pcs and params.pcs ~= "" then - result.tls_CertSha = params.pcs - end - if params.vcn and params.vcn ~= "" then - result.tls_CertByName = params.vcn - end - if result.transport == "ws" then - result.ws_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil - result.ws_path = params.path and UrlDecode(params.path) or "/" - elseif result.transport == "httpupgrade" then - result.httpupgrade_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil - result.httpupgrade_path = params.path and UrlDecode(params.path) or "/" - elseif result.transport == "xhttp" or result.transport == "splithttp" then - result.xhttp_mode = params.mode or "auto" - result.xhttp_host = params.host and UrlDecode(params.host) or nil - result.xhttp_path = params.path and UrlDecode(params.path) or "/" - -- 检查 extra 参数是否存在且非空 - if params.extra and params.extra ~= "" then - result.enable_xhttp_extra = "1" - result.xhttp_extra = base64Encode(params.extra) - end - -- 尝试解析 JSON 数据 - local success, Data = pcall(jsonParse, params.extra or "") - if success and type(Data) == "table" then - local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address) - or (Data.downloadSettings and Data.downloadSettings.address) - result.download_address = (address and address ~= "") and address:gsub("^%[", ""):gsub("%]$", "") - else - -- 如果解析失败,清空下载地址 - result.download_address = nil - end - elseif result.transport == "http" or result.transport == "h2" then - result.transport = "h2" - result.h2_host = params.host and UrlDecode(params.host) or nil - result.h2_path = params.path and UrlDecode(params.path) or nil - elseif result.transport == "kcp" then - result.kcp_guise = params.headerType or "none" - if params.headerType and params.headerType == "dns" then - result.kcp_domain = params.host or "" - end - result.seed = params.seed - elseif result.transport == "quic" then - result.quic_guise = params.headerType or "none" - result.quic_security = params.quicSecurity or "none" - result.quic_key = params.key - elseif result.transport == "grpc" then - result.serviceName = params.serviceName - result.grpc_mode = params.mode or "gun" - elseif result.transport == "tcp" or result.transport == "raw" then - result.tcp_guise = params.headerType and params.headerType ~= "" and params.headerType or "none" - if result.tcp_guise == "http" then - result.tcp_host = params.host and UrlDecode(params.host) or nil - result.tcp_path = params.path and UrlDecode(params.path) or nil - end - end - else - result.type = "trojan" end elseif szType == "vless" then local url = URL.parse("http://" .. content) @@ -1137,7 +1260,7 @@ local function processData(szType, content, cfgid) result.alias = raw_alias -- 临时赋值(后面会被覆盖) result.type = "v2ray" result.v2ray_protocol = "vless" - result.server = url.host + result.server = normalize_host(url.host) result.server_port = url.port result.vmess_id = url.user result.vless_encryption = params.encryption or "none" @@ -1267,6 +1390,11 @@ local function processData(szType, content, cfgid) end end elseif szType == "tuic" then + if not tuic_type then + log("跳过 TUIC 节点:本地未安装 mihomo。") + return nil + end + -- 提取别名(如果存在) local alias = "" if content:find("#") then @@ -1303,13 +1431,7 @@ local function processData(szType, content, cfgid) end -- 提取服务器地址和端口 - if host_port:find(":") then - local sp = split(host_port, ":") - result.server_port = sp[#sp] - result.server = sp[1] - else - result.server = host_port - end + result.server, result.server_port = parse_host_port(host_port, "443") result.type = tuic_type result.tuic_ip = params.ip or "" @@ -1374,6 +1496,10 @@ local function processData(szType, content, cfgid) end end + if not result.type or result.type == "" or result.type == "0" then + return nil + end + if not result.alias then if result.server and result.server_port then result.alias = result.server .. ':' .. result.server_port @@ -1426,51 +1552,256 @@ end -- curl local function curl(url, user_agent) - if not url or url == "" then - return "", nil - end - - -- 清理 URL + -- 清理 URL 中的隐藏字符和前后空白 url = url:gsub("%s+$", ""):gsub("^%s+", ""):gsub("%z", ""):gsub("[\r\n]", "") - -- 处理 user_agent 参数 local ua_opt = "" if user_agent and user_agent ~= "" then - -- 安全 shell quoting - local safe_ua = string.format("%q", user_agent) - ua_opt = "-A " .. safe_ua - else - -- 默认 UA(避免被拦) - ua_opt = '-A "Mozilla/5.0"' + -- 转义双引号,防止破坏 -A 参数 + local safe_ua = user_agent:gsub("[\r\n]", ""):gsub('[\\"`$]', '\\%0') -- 安全转义 + ua_opt = '-A "' .. safe_ua .. '"' end - + -- 安全转义 URL:用单引号包裹,并转义内部的单引号 + local safe_url = "'" .. url:gsub("'", "'\\''") .. "'" local cmd = string.format( - 'curl -fskL --retry 3 --connect-timeout 3 --max-time 30 ' .. - '-H "Accept-Encoding: identity" %s ' .. - '-w "%%{http_code}" "%s"', + 'curl -sSL --http1.1 --connect-timeout 20 --max-time 30 --retry 3 -H "Accept-Encoding: identity" %s --insecure --location %s', ua_opt, - url + safe_url ) - - local result = luci.sys.exec(cmd) or "" - result = trim(result) - - if result == "" then - return "", nil - end - - -- 解析 HTTP code(最后3位) - local stdout = result:sub(1, -4) - local code = tonumber(result:sub(-3)) - - if code ~= 200 then - return "", code - end - - local md5 = md5_string(stdout) + -- 执行命令并获取输出 + local stdout = luci.sys.exec(cmd) + stdout = trim(stdout) -- 确保 trim 函数存在 + local md5 = md5_string(stdout) -- 确保 md5_string 函数存在 return stdout, md5 end +local function collect_wan_interfaces() + local ifaces = {} + local seen = {} + + local function add_iface(value) + value = trim(value or "") + if value == "" or value == "nil" or value:sub(1, 1) == "@" then + return + end + + if value:find("%s") then + for iface in value:gmatch("%S+") do + add_iface(iface) + end + return + end + + if not seen[value] then + seen[value] = true + ifaces[#ifaces + 1] = value + end + end + + local function add_iface_from_status(netif) + local raw = trim(luci.sys.exec(string.format( + "ubus -S call network.interface.%s status 2>/dev/null", + netif + ))) + if raw == "" then + return + end + + local status = jsonParse(raw) + if type(status) ~= "table" then + return + end + + add_iface(status.l3_device) + add_iface(status.device) + end + + add_iface_from_status("wan") + add_iface_from_status("wan6") + + if #ifaces == 0 then + add_iface(ucic:get("network", "wan", "device")) + add_iface(ucic:get("network", "wan", "ifname")) + add_iface(ucic:get("network", "wan6", "device")) + add_iface(ucic:get("network", "wan6", "ifname")) + end + + if #ifaces == 0 then + local route_output = luci.sys.exec("ip route show default 2>/dev/null") + for iface in route_output:gmatch("dev%s+(%S+)") do + add_iface(iface) + end + end + + if #ifaces == 0 then + local route6_output = luci.sys.exec("ip -6 route show default 2>/dev/null") + for iface in route6_output:gmatch("dev%s+(%S+)") do + add_iface(iface) + end + end + + return ifaces +end + +local function detect_subscribe_bypass_backend() + if luci.sys.call("nft list chain inet ss_spec ss_spec_output >/dev/null 2>&1") == 0 then + return "nftables" + end + + local ipt_output = luci.sys.exec("iptables -t nat -S OUTPUT 2>/dev/null") + if ipt_output:find("SS_SPEC_WAN_AC", 1, true) or ipt_output:find("SS_SPEC_ROUTER", 1, true) then + return "iptables" + end + + return nil +end + +local function create_direct_subscribe_bypass() + if proxy ~= "0" then + return nil + end + + local backend = detect_subscribe_bypass_backend() + if not backend then + log("直连订阅: 未检测到 SSR 路由器自身 OUTPUT 代理链,跳过临时绕过规则。") + return nil + end + + local wan_ifaces = collect_wan_interfaces() + local targets = (#wan_ifaces > 0) and wan_ifaces or {false} + local tag = string.format("SSR_SUB_BYPASS_%d", tonumber(nixio.getpid()) or os.time()) + local manager = { + backend = backend, + tag = tag, + targets = targets, + added = false + } + + function manager:describe_target(target) + if target then + return target + end + return "all-output" + end + + function manager:apply_iptables_rule(target) + local cmd = { + "iptables -t nat -I OUTPUT 1" + } + + if target then + cmd[#cmd + 1] = "-o " .. shell_quote(target) + end + + cmd[#cmd + 1] = "-p tcp -m multiport --dports 80,443" + cmd[#cmd + 1] = "-m comment --comment " .. shell_quote(self.tag) + cmd[#cmd + 1] = "-j RETURN >/dev/null 2>&1" + + return luci.sys.call(table.concat(cmd, " ")) == 0 + end + + function manager:apply_nft_rule(target) + local rule = { + "nft insert rule inet ss_spec ss_spec_output" + } + + if target then + rule[#rule + 1] = "oifname " .. nft_string_literal(target) + end + + rule[#rule + 1] = "meta l4proto tcp tcp dport { 80, 443 }" + rule[#rule + 1] = "counter return" + rule[#rule + 1] = "comment " .. nft_string_literal(self.tag) + + return luci.sys.call(table.concat(rule, " ") .. " >/dev/null 2>&1") == 0 + end + + function manager:apply() + for index = #self.targets, 1, -1 do + local target = self.targets[index] + local ok + + if self.backend == "nftables" then + ok = self:apply_nft_rule(target) + else + ok = self:apply_iptables_rule(target) + end + + if ok then + self.added = true + log("直连订阅: 已添加临时绕过规则 -> " .. self.backend .. " / " .. self:describe_target(target)) + else + log("直连订阅: 添加临时绕过规则失败 -> " .. self.backend .. " / " .. self:describe_target(target)) + end + end + + if not self.added then + log("直连订阅: 临时绕过规则未生效,订阅请求仍可能走代理。") + end + end + + function manager:cleanup_nft_rules() + local output = luci.sys.exec("nft -a list chain inet ss_spec ss_spec_output 2>/dev/null") + local pattern = 'comment "' .. escape_lua_pattern(self.tag) .. '".-# handle (%d+)' + local handles = {} + + for handle in output:gmatch(pattern) do + handles[#handles + 1] = handle + end + + for _, handle in ipairs(handles) do + luci.sys.call(string.format( + "nft delete rule inet ss_spec ss_spec_output handle %s >/dev/null 2>&1", + handle + )) + end + + return #handles + end + + function manager:cleanup_iptables_rules() + local removed = 0 + + for _, target in ipairs(self.targets) do + local cmd = { + "iptables -t nat -D OUTPUT" + } + + if target then + cmd[#cmd + 1] = "-o " .. shell_quote(target) + end + + cmd[#cmd + 1] = "-p tcp -m multiport --dports 80,443" + cmd[#cmd + 1] = "-m comment --comment " .. shell_quote(self.tag) + cmd[#cmd + 1] = "-j RETURN >/dev/null 2>&1" + + if luci.sys.call(table.concat(cmd, " ")) == 0 then + removed = removed + 1 + end + end + + return removed + end + + function manager:cleanup() + if not self.added then + return + end + + local removed + if self.backend == "nftables" then + removed = self:cleanup_nft_rules() + else + removed = self:cleanup_iptables_rules() + end + + log("直连订阅: 已清理临时绕过规则数量: " .. tostring(removed or 0)) + self.added = false + end + + return manager +end + local function check_filer(result) -- 过滤的关键词列表 local filter_word = split(filter_words, "/") @@ -1527,11 +1858,76 @@ local function loadOldNodes(groupHash) end) end +local function get_section_ss_backend(section) + if not section then + return nil + end + if section.type == "ss" or section.type == "ss-libev" then + return "ss" + end + if section.type == "ss-rust" then + return "ss-rust" + end + if section.type == "v2ray" and section.v2ray_protocol == "shadowsocks" then + return "v2ray" + end + return nil +end + +local function group_needs_ss_backend_refresh(groupHash) + local preferred = preferred_ss_backend() + local has_ss_node = false + local needs_refresh = false + + if not preferred then + return false + end + + ucic:foreach(name, uciType, function(s) + if s.grouphashkey ~= groupHash then + return + end + + local current = get_section_ss_backend(s) + if current then + has_ss_node = true + if current ~= preferred then + needs_refresh = true + return false + end + end + end) + + return has_ss_node and needs_refresh +end + +local function preserve_unselected_groups(selected_hashes) + local preserved = {} + + ucic:foreach(name, uciType, function(s) + local groupHash = s.grouphashkey + if groupHash and groupHash ~= "" and not selected_hashes[groupHash] and not preserved[groupHash] then + preserved[groupHash] = true + loadOldNodes(groupHash) + end + end) +end + local execute = function() local updated = false - local service_stopped = false - for k, url in ipairs(subscribe_url) do - local raw, new_md5 = curl(url) + local selected_hashes = {} + + for _, item in ipairs(subscribe_items) do + selected_hashes[md5(item.url)] = true + end + + if target_subscribe_sid ~= "" then + preserve_unselected_groups(selected_hashes) + end + + for _, item in ipairs(subscribe_items) do + local url = item.url + local raw, new_md5 = curl(url, user_agent) log("raw 长度: "..#raw) local groupHash = md5(url) local old_md5 = read_old_md5(groupHash) @@ -1541,8 +1937,11 @@ local execute = function() log("old_md5: " .. tostring(old_md5)) log("new_md5: " .. tostring(new_md5)) - if #raw > 0 then - if old_md5 and new_md5 == old_md5 then + local backend_refresh = group_needs_ss_backend_refresh(groupHash) + if #raw == 0 then + log(url .. ': 获取内容为空') + loadOldNodes(groupHash) + elseif old_md5 and new_md5 == old_md5 and not backend_refresh then log("订阅未变化, 跳过无需更新的订阅: " .. url) -- 防止 diff 阶段误删未更新订阅节点 loadOldNodes(groupHash) @@ -1552,26 +1951,34 @@ local execute = function() -- tinsert(nodeResult[index], s) -- end --end) - else + else + if backend_refresh and old_md5 and new_md5 == old_md5 then + log("检测到 SS 后端偏好变化,强制重建订阅节点: " .. url) + end updated = true -- 保存更新后的 MD5 值到以 groupHash 为标识的临时文件中,用于下次订阅更新时进行对比 write_new_md5(groupHash, new_md5) - -- 暂停服务(仅当 MD5 有变化时才执行) - if proxy == '0' and not service_stopped then - log('服务正在暂停') - luci.sys.init.stop(name) - service_stopped = true - end - cache[groupHash] = {} tinsert(nodeResult, {}) local index = #nodeResult local nodes, szType + local is_clash_subscription = false - -- SSD 似乎是这种格式 ssd:// 开头的 - if raw:find('ssd://') then - szType = 'ssd' + if isClashYAML(raw) then + is_clash_subscription = true + local result = processClashSubscription(url) + if result and not check_filer(result) and not cache[groupHash][result.hashkey] then + result.grouphashkey = groupHash + table.insert(nodeResult[index], result) + cache[groupHash][result.hashkey] = result + log('成功导入 Clash 总节点: ' .. result.alias) + else + log('丢弃无效 Clash 总节点: ' .. url) + end + -- SSD 似乎是这种格式 ssd:// 开头的 + elseif raw:find('ssd://') then + szType = 'ssd' local nEnd = select(2, raw:find('ssd://')) nodes = base64Decode(raw:sub(nEnd + 1, #raw)) nodes = jsonParse(nodes) @@ -1599,9 +2006,45 @@ local execute = function() nodes = split(base64Decode(raw):gsub("\r\n", "\n"), "\n") end + if not is_clash_subscription and not szType and type(nodes) == "table" then + local anytls_nodes = {} + local normal_nodes = {} + for _, node in ipairs(nodes) do + local line = trim(node or "") + if line:match("^anytls://") then + local parsed = parseAnytlsShare(line:gsub("^anytls://", "")) + if parsed then + table.insert(anytls_nodes, parsed) + end + elseif line ~= "" then + table.insert(normal_nodes, node) + end + end + + if #anytls_nodes > 0 then + local parsed_url = URL.parse(url) + local alias = "Clash_" .. (parsed_url.host or groupHash) + local local_path = string.format("%s/%s.anytls.yaml", local_clash_dir, groupHash) + local yaml = buildAnytlsClashYaml(anytls_nodes, "Proxy") + nixio.fs.mkdirr(local_clash_dir) + nixio.fs.writefile(local_path, yaml) + + local result = processLocalClashSubscription(local_path, alias) + if result and not cache[groupHash][result.hashkey] then + result.grouphashkey = groupHash + table.insert(nodeResult[index], result) + cache[groupHash][result.hashkey] = result + log('成功导入 AnyTLS 转 Clash 总节点: ' .. result.alias) + end + end + + nodes = normal_nodes + end + -- 临时存储该订阅解析出的节点(带原始别名) local groupRawNodes = {} + if not is_clash_subscription then for _, v in ipairs(nodes) do if v and not string.match(v, "^%s*$") then xpcall(function() @@ -1630,12 +2073,12 @@ local execute = function() -- log(result) if result then -- 中文做地址的 也没有人拿中文域名搞,就算中文域也有Puny Code SB 机场 - if not result.server or not result.server_port - or result.server == "127.0.0.1" - or result.alias == "NULL" - or check_filer(result) - or result.server:match("[^0-9a-zA-Z%-_%.%s]") - or cache[groupHash][result.hashkey] then + if not result.server or not result.server_port + or (result.type ~= "clash" and result.server == "127.0.0.1") + or result.alias == "NULL" + or check_filer(result) + or (result.type ~= "clash" and result.server:match("[^0-9a-zA-Z%-_%.%s]")) + or cache[groupHash][result.hashkey] then log('丢弃无效节点: ' .. result.alias) else -- 暂存节点 @@ -1647,6 +2090,7 @@ local execute = function() end) end end + end -- 对该组节点进行别名编号:重复节点加后缀,唯一节点不加 local freq = {} @@ -1673,9 +2117,6 @@ local execute = function() end log('成功解析节点数量: ' .. #groupRawNodes) - end - else - log(url .. ': 获取内容为空') end end -- 输出日志并判断是否需要进行 diff @@ -1688,10 +2129,6 @@ local execute = function() -- diff 阶段 if next(nodeResult) == nil then log("更新失败,没有可用的节点信息") - if proxy == '0' then - luci.sys.init.start(name) - log('订阅失败, 恢复服务') - end return end local add, del = 0, 0 @@ -1740,37 +2177,21 @@ local execute = function() return next_sid end + local sid = ucic:add(name, uciType) + ucic:delete(name, sid) for _, v in ipairs(nodeResult) do for _, vv in ipairs(v) do if not vv._ignore then - local sid = ucic:add(name, uciType) + --local sid = ucic:add(name, uciType) if sid then local suffix = sid:sub(-4) - ucic:delete(name, sid) + --ucic:delete(name, sid) local id = get_next_sid() local cfgid = string.format("cfg%02x%s", id, suffix) local section = ucic:section(name, uciType, cfgid) if section then ucic:tset(name, section, vv) ucic:set(name, section, "switch_enable", switch) - -- 为 Xray 节点添加域名解析配置 - if vv.type == "v2ray" then - if domain_resolver and domain_resolver ~= "" then - ucic:set(name, section, "domain_resolver", domain_resolver) - if domain_resolver == "https" then - if domain_resolver_dns_https and domain_resolver_dns_https ~= "" then - ucic:set(name, section, "domain_resolver_dns_https", domain_resolver_dns_https) - end - else - if domain_resolver_dns and domain_resolver_dns ~= "" then - ucic:set(name, section, "domain_resolver_dns", domain_resolver_dns) - end - end - end - if domain_strategy and domain_strategy ~= "" then - ucic:set(name, section, "domain_strategy", domain_strategy) - end - end add = add + 1 end end @@ -1803,11 +2224,20 @@ local execute = function() log('订阅更新成功') end -if subscribe_url and #subscribe_url > 0 then +if subscribe_items and #subscribe_items > 0 then + if proxy == "1" then + log("当前订阅模式: 通过代理订阅") + else + log("当前订阅模式: 不通过代理订阅") + end + local direct_bypass = create_direct_subscribe_bypass() + if direct_bypass then + direct_bypass:apply() + end xpcall(execute, function(e) log(e) log(debug.traceback()) - log('发生错误, 正在恢复服务') + log('发生错误, 正在尝试恢复服务状态') local firstServer = ucic:get_first(name, uciType) if firstServer then luci.sys.call("/etc/init.d/" .. name .. " restart > /dev/null 2>&1 &") -- 不加&的话日志会出现的更早 @@ -1817,8 +2247,7 @@ if subscribe_url and #subscribe_url > 0 then log('停止服务成功') end end) + if direct_bypass then + direct_bypass:cleanup() + end end - -reload_service() { - restart -} diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua index 34d13dc89..ede40fa56 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua @@ -250,10 +250,6 @@ if args then update(uci:get_first("shadowsocksr", "global", "adblock_url"), "/etc/ssrplus/ad.conf", args, TMP_DNSMASQ_PATH .. "/ad.conf") os.exit(0) end - if args == "nfip_data" then - update(uci:get_first("shadowsocksr", "global", "nfip_url"), "/etc/ssrplus/netflixip.list", args, TMP_DNSMASQ_PATH .. "/netflixip.list") - os.exit(0) - end else log("正在更新【GFW列表】数据库") update(uci:get_first("shadowsocksr", "global", "gfwlist_url"), "/etc/ssrplus/gfw_list.conf", "gfw_data", TMP_DNSMASQ_PATH .. "/gfw_list.conf") @@ -267,10 +263,4 @@ else log("正在更新【广告屏蔽】数据库") update(uci:get_first("shadowsocksr", "global", "adblock_url"), "/etc/ssrplus/ad.conf", "ad_data", TMP_DNSMASQ_PATH .. "/ad.conf") end - if uci:get_first("shadowsocksr", "global", "netflix_enable", "0") == "1" then - log("正在更新【Netflix IP段】数据库") - update(uci:get_first("shadowsocksr", "global", "nfip_url"), "/etc/ssrplus/netflixip.list", "nfip_data", TMP_DNSMASQ_PATH .. "/netflixip.list") - end - -- log("正在更新【Netflix IP段】数据库") - -- update(uci:get_first("shadowsocksr", "global", "nfip_url"), "/etc/ssrplus/netflixip.list", "nfip_data") end diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/update_components.sh b/luci-app-ssr-plus/root/usr/share/shadowsocksr/update_components.sh new file mode 100755 index 000000000..299e81803 --- /dev/null +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/update_components.sh @@ -0,0 +1,1428 @@ +#!/bin/sh + +set -u + +XRAY_RELEASE_PAGE="https://github.com/XTLS/Xray-core/releases/latest" +MIHOMO_RELEASE_PAGE="https://github.com/MetaCubeX/mihomo/releases/latest" +NAIVEPROXY_RELEASE_API="https://api.github.com/repos/klzgrad/naiveproxy/releases/latest" +XRAY_BINARY="/usr/bin/xray" +MIHOMO_BINARY="/usr/bin/mihomo" +NAIVEPROXY_BINARY="/usr/bin/naive" +COUNTRY_MMDB_URL="https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb" +GEOSITE_URL="https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat" +GEOIP_DAT_URL="https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat" +COUNTRY_MMDB_FILE="/usr/share/shadowsocksr/Country.mmdb" +GEOIP_DAT_FILE="/usr/share/v2ray/geoip.dat" +GEOSITE_DAT_FILE="/usr/share/v2ray/geosite.dat" +OPENCLASH_GEOSITE_FILE="/etc/openclash/GeoSite.dat" +OPENCLASH_GEOIP_DAT_FILE="/etc/openclash/geoip.dat" +OPENCLASH_GEOSITE_DAT_FILE="/etc/openclash/geosite.dat" + +log_kv() { + key="$1" + shift + printf '%s=%s\n' "$key" "$*" +} + +file_mtime() { + local path="$1" + [ -f "$path" ] || { + printf '%s' 'File Not Exist' + return 0 + } + date -r "$path" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || printf '%s' 'Unknown' +} + +file_size() { + local path="$1" + [ -f "$path" ] || { + printf '%s' '0' + return 0 + } + wc -c < "$path" 2>/dev/null | tr -d '[:space:]' +} + +resolve_existing_geo_file() { + local primary="$1" + local fallback="$2" + + if [ -f "$primary" ]; then + printf '%s' "$primary" + elif [ -n "$fallback" ] && [ -f "$fallback" ]; then + printf '%s' "$fallback" + else + printf '%s' "$primary" + fi +} + +resolve_geosite_file() { + resolve_existing_geo_file "$GEOSITE_DAT_FILE" "$OPENCLASH_GEOSITE_FILE" +} + +resolve_v2ray_geoip_file() { + resolve_existing_geo_file "$GEOIP_DAT_FILE" "$OPENCLASH_GEOIP_DAT_FILE" +} + +resolve_v2ray_geosite_file() { + resolve_existing_geo_file "$GEOSITE_DAT_FILE" "$OPENCLASH_GEOSITE_DAT_FILE" +} + +trim_version() { + printf '%s' "$1" | sed 's/^v//' +} + +get_component_mirror() { + if [ -n "${COMPONENT_MIRROR:-}" ]; then + echo "$COMPONENT_MIRROR" + return 0 + fi + uci -q get shadowsocksr.@global[0].component_mirror 2>/dev/null || echo "direct" +} + +mirror_wrap_url() { + local raw_url="$1" + local mirror + + mirror="$(get_component_mirror)" + case "$mirror" in + direct|"") + printf '%s' "$raw_url" + ;; + ghproxy) + printf 'https://mirror.ghproxy.com/%s' "$raw_url" + ;; + ghproxy_cc) + printf 'https://ghproxy.cc/%s' "$raw_url" + ;; + ghfast) + printf 'https://ghfast.top/%s' "$raw_url" + ;; + jsdelivr) + case "$raw_url" in + https://github.com/XTLS/Xray-core/releases/download/*) + printf '%s' "$raw_url" | sed 's#https://github.com/XTLS/Xray-core/releases/download/\(v[^/]*\)/\(.*\)#https://fastly.jsdelivr.net/gh/XTLS/Xray-core@\1/\2#' + ;; + https://github.com/MetaCubeX/mihomo/releases/download/*) + printf '%s' "$raw_url" | sed 's#https://github.com/MetaCubeX/mihomo/releases/download/\(v[^/]*\)/\(.*\)#https://fastly.jsdelivr.net/gh/MetaCubeX/mihomo@\1/\2#' + ;; + *) + printf '%s' "$raw_url" + ;; + esac + ;; + *) + printf '%s' "$raw_url" + ;; + esac +} + +version_gt() { + local left right first + + left="$(trim_version "${1:-}")" + right="$(trim_version "${2:-}")" + + [ -n "$left" ] || return 1 + [ -n "$right" ] || return 1 + [ "$left" = "$right" ] && return 1 + + first="$(printf '%s\n%s\n' "$left" "$right" | sort -V | tail -n 1)" + [ "$first" = "$left" ] +} + +naiveproxy_versions_equal() { + local left right + + left="$(trim_version "${1:-}")" + right="$(trim_version "${2:-}")" + [ -n "$left" ] || return 1 + [ -n "$right" ] || return 1 + [ "$left" = "$right" ] && return 0 + [ "${left%%-*}" = "${right%%-*}" ] +} + +get_openwrt_arch() { + local arch + + arch="" + if [ -r /etc/openwrt_release ]; then + arch="$(. /etc/openwrt_release 2>/dev/null; printf '%s' "${DISTRIB_ARCH:-}")" + fi + + if [ -z "$arch" ] && command -v opkg >/dev/null 2>&1; then + arch="$(opkg print-architecture 2>/dev/null | awk '$2 != "all" && $2 != "noarch" { print $2 }' | tail -n 1)" + fi + + if [ -z "$arch" ] && command -v uname >/dev/null 2>&1; then + arch="$(uname -m 2>/dev/null)" + fi + + printf '%s' "$arch" +} + +is_openwrt_env() { + [ -r /etc/openwrt_release ] || command -v opkg >/dev/null 2>&1 +} + +find_mihomo_binary() { + if command -v mihomo >/dev/null 2>&1; then + command -v mihomo + return 0 + fi + + for path in /usr/bin/mihomo /usr/libexec/mihomo /etc/ssrplus/bin/mihomo; do + if [ -x "$path" ]; then + printf '%s' "$path" + return 0 + fi + done + + return 1 +} + +find_naiveproxy_binary() { + if command -v naive >/dev/null 2>&1; then + command -v naive + return 0 + fi + + for path in /usr/bin/naive /usr/libexec/naive /etc/ssrplus/bin/naive; do + if [ -x "$path" ]; then + printf '%s' "$path" + return 0 + fi + done + + return 1 +} + +get_xray_current_version() { + if [ ! -x "$XRAY_BINARY" ]; then + return 1 + fi + + "$XRAY_BINARY" version 2>/dev/null | sed -n 's/^Xray[[:space:]]\+\([^[:space:]]\+\).*$/\1/p' | sed -n '1p' + return 0 +} + +get_mihomo_current_version() { + local binary + + binary="$(find_mihomo_binary)" || return 1 + "$binary" -v 2>/dev/null | sed -n 's/.* v\([0-9][0-9.]*\).*/\1/p' | sed -n '1p' + return 0 +} + +get_naiveproxy_current_version() { + local binary + + binary="$(find_naiveproxy_binary)" || return 1 + "$binary" --version 2>&1 | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?' | sed -n '1p' + return 0 +} + +map_xray_asset() { + case "$1" in + x86_64*|amd64*) + printf '%s' 'Xray-linux-64.zip' + ;; + i386*|i486*|i586*|i686*|x86*) + printf '%s' 'Xray-linux-32.zip' + ;; + aarch64*|arm64*) + printf '%s' 'Xray-linux-arm64-v8a.zip' + ;; + *armv7*|*cortex-a*|*neon*|*vfpv3*|*vfpv4*) + printf '%s' 'Xray-linux-arm32-v7a.zip' + ;; + *armv6*|*arm1176*) + printf '%s' 'Xray-linux-arm32-v6.zip' + ;; + *armv5*|*arm926*|*xscale*) + printf '%s' 'Xray-linux-arm32-v5.zip' + ;; + mips64el*|mips64le*) + printf '%s' 'Xray-linux-mips64le.zip' + ;; + mips64*) + printf '%s' 'Xray-linux-mips64.zip' + ;; + mipsel*|mips32el*|mips32le*) + printf '%s' 'Xray-linux-mips32le.zip' + ;; + mips*) + printf '%s' 'Xray-linux-mips32.zip' + ;; + riscv64*) + printf '%s' 'Xray-linux-riscv64.zip' + ;; + loongarch64*|loong64*) + printf '%s' 'Xray-linux-loong64.zip' + ;; + powerpc64*|ppc64*) + printf '%s' 'Xray-linux-ppc64.zip' + ;; + s390x*) + printf '%s' 'Xray-linux-s390x.zip' + ;; + *) + return 1 + ;; + esac +} + +require_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +select_unzip_cmd() { + if require_cmd unzip; then + printf '%s' 'unzip -oq' + return 0 + fi + + if busybox unzip >/dev/null 2>&1; then + printf '%s' 'busybox unzip -o' + return 0 + fi + + return 1 +} + +select_gzip_cmd() { + if require_cmd gzip; then + printf '%s' 'gzip -dc' + return 0 + fi + + if busybox gzip >/dev/null 2>&1; then + printf '%s' 'busybox gzip -dc' + return 0 + fi + + return 1 +} + +select_xz_cmd() { + if require_cmd xz; then + printf '%s' 'xz -dc' + return 0 + fi + + if busybox xz >/dev/null 2>&1; then + printf '%s' 'busybox xz -dc' + return 0 + fi + + return 1 +} + +select_tar_cmd() { + if require_cmd tar; then + printf '%s' 'tar' + return 0 + fi + + if busybox tar --help >/dev/null 2>&1; then + printf '%s' 'busybox tar' + return 0 + fi + + return 1 +} + +select_wget_cmd() { + if require_cmd wget-ssl; then + printf '%s' 'wget-ssl' + return 0 + fi + + if require_cmd wget; then + printf '%s' 'wget' + return 0 + fi + + return 1 +} + +curl_effective_url() { + local target="$1" + local effective="" + local headers="" + local location="" + + headers="$(curl -kfsSI --http1.1 --connect-timeout 10 --retry 2 -A 'curl/8.0' -H 'Accept-Encoding: identity' "$target" 2>/dev/null || true)" + location="$(printf '%s\n' "$headers" | sed -n 's/^[Ll]ocation:[[:space:]]*//p' | sed 's/\r$//' | sed 's/[[:space:]]\+\[following\]$//' | sed -n '1p')" + [ -n "$location" ] && effective="$location" + [ -n "$effective" ] || effective="$(curl -kfsSL --http1.1 --connect-timeout 10 --retry 2 -A 'curl/8.0' -H 'Accept-Encoding: identity' -o /dev/null -w '%{url_effective}' "$target" 2>/dev/null || true)" + [ -n "$effective" ] || return 1 + + printf '%s' "$effective" +} + +wget_effective_url() { + local target="$1" + local wget_cmd output location + + wget_cmd="$(select_wget_cmd)" || return 1 + output="$($wget_cmd --server-response --max-redirect=0 --spider --timeout=20 --tries=3 --no-check-certificate "$target" 2>&1 || true)" + location="$(printf '%s\n' "$output" | sed -n 's/^[Ll]ocation:[[:space:]]*//p' | sed 's/\r$//' | sed 's/[[:space:]]\+\[following\]$//' | sed -n '1p')" + + if [ -n "$location" ]; then + printf '%s' "$location" + return 0 + fi + + printf '%s\n' "$output" | grep -qE 'HTTP/[0-9.]+ 200' || return 1 + printf '%s' "$target" +} + +effective_url() { + local target="$1" + local url="" + + url="$(curl_effective_url "$target" 2>/dev/null || true)" + [ -n "$url" ] || url="$(wget_effective_url "$target" 2>/dev/null || true)" + [ -n "$url" ] || return 1 + printf '%s' "$url" +} + +fetch_text() { + local url="$1" + local wget_cmd + + if curl -kfsSL --http1.1 --connect-timeout 10 --retry 2 -A 'curl/8.0' -H 'Accept: application/vnd.github+json' "$url" 2>/dev/null; then + return 0 + fi + + wget_cmd="$(select_wget_cmd)" || return 1 + "$wget_cmd" --header='Accept: application/vnd.github+json' --timeout=20 --tries=3 --no-check-certificate -O - "$url" 2>/dev/null +} + +download_file() { + local url="$1" + local output="$2" + local wget_cmd + + if curl -kfsSL --http1.1 --connect-timeout 10 --retry 2 -A 'curl/8.0' -H 'Accept-Encoding: identity' -o "$output" "$url" 2>/dev/null; then + return 0 + fi + + wget_cmd="$(select_wget_cmd)" || return 1 + "$wget_cmd" --no-check-certificate --timeout=20 --tries=3 -O "$output" "$url" >/dev/null 2>&1 +} + +geo_validate_download() { + local new_file="$1" + local current_file="$2" + local new_size current_size + + new_size="$(file_size "$new_file")" + [ "${new_size:-0}" -gt 0 ] 2>/dev/null || return 1 + + if [ -f "$current_file" ]; then + current_size="$(file_size "$current_file")" + [ "${new_size:-0}" -ge "${current_size:-0}" ] 2>/dev/null || return 1 + fi + + return 0 +} + +geo_safe_replace() { + local new_file="$1" + local current_file="$2" + local backup_file="$3" + + mkdir -p "$(dirname "$current_file")" || return 1 + [ -f "$current_file" ] && cp -fp "$current_file" "$backup_file" 2>/dev/null || true + + if ! cp -f "$new_file" "$current_file"; then + [ -f "$backup_file" ] && cp -f "$backup_file" "$current_file" 2>/dev/null || true + return 1 + fi + + return 0 +} + +geo_local_info() { + local geo="$1" + local file1="" + local file2="" + + case "$geo" in + country_mmdb) + file1="$COUNTRY_MMDB_FILE" + ;; + geosite) + file1="$(resolve_geosite_file)" + ;; + v2ray_geo) + file1="$(resolve_v2ray_geoip_file)" + file2="$(resolve_v2ray_geosite_file)" + ;; + *) + log_kv error 'unsupported_component' + return 1 + ;; + esac + + log_kv component "$geo" + log_kv installed "$([ -f "$file1" ] && echo 1 || echo 0)" + log_kv current_version "$(file_mtime "$file1")" + log_kv current_version_extra "$([ -n "$file2" ] && file_mtime "$file2" || echo '')" + log_kv latest_version '' + log_kv can_upgrade 0 + log_kv error '' +} + +geo_upgrade() { + local geo="$1" + local tmp_dir file_a url_a file_b url_b msg + + tmp_dir="$(mktemp -d /tmp/ssrplus-geo.XXXXXX)" + [ -n "$tmp_dir" ] && [ -d "$tmp_dir" ] || { + log_kv component "$geo" + log_kv success 0 + log_kv message 'Failed to create temp directory' + return 0 + } + trap "rm -rf '$tmp_dir'" EXIT INT TERM + + case "$geo" in + country_mmdb) + file_a="$COUNTRY_MMDB_FILE" + url_a="$(mirror_wrap_url "$COUNTRY_MMDB_URL")" + download_file "$url_a" "$tmp_dir/file_a" || { + log_kv component "$geo" + log_kv success 0 + log_kv message 'Download failed' + return 0 + } + geo_validate_download "$tmp_dir/file_a" "$file_a" || { + log_kv component "$geo" + log_kv success 1 + log_kv current_version "$(file_mtime "$file_a")" + log_kv current_version_extra '' + log_kv latest_version "$(file_mtime "$file_a")" + log_kv can_upgrade 0 + log_kv message 'Already up to date' + return 0 + } + geo_safe_replace "$tmp_dir/file_a" "$file_a" "$tmp_dir/file_a.bak" || { + log_kv component "$geo" + log_kv success 0 + log_kv message 'Install failed' + return 0 + } + msg='Upgrade completed' + ;; + geosite) + file_a="$(resolve_geosite_file)" + url_a="$(mirror_wrap_url "$GEOSITE_URL")" + download_file "$url_a" "$tmp_dir/file_a" || { + log_kv component "$geo" + log_kv success 0 + log_kv message 'Download failed' + return 0 + } + geo_validate_download "$tmp_dir/file_a" "$file_a" || { + log_kv component "$geo" + log_kv success 1 + log_kv current_version "$(file_mtime "$file_a")" + log_kv current_version_extra '' + log_kv latest_version "$(file_mtime "$file_a")" + log_kv can_upgrade 0 + log_kv message 'Already up to date' + return 0 + } + geo_safe_replace "$tmp_dir/file_a" "$file_a" "$tmp_dir/file_a.bak" || { + log_kv component "$geo" + log_kv success 0 + log_kv message 'Install failed' + return 0 + } + msg='Upgrade completed' + ;; + v2ray_geo) + file_a="$(resolve_v2ray_geoip_file)" + url_a="$(mirror_wrap_url "$GEOIP_DAT_URL")" + file_b="$(resolve_v2ray_geosite_file)" + url_b="$(mirror_wrap_url "$GEOSITE_URL")" + download_file "$url_a" "$tmp_dir/file_a" || { + log_kv component "$geo" + log_kv success 0 + log_kv message 'Download failed' + return 0 + } + download_file "$url_b" "$tmp_dir/file_b" || { + log_kv component "$geo" + log_kv success 0 + log_kv message 'Download failed' + return 0 + } + geo_validate_download "$tmp_dir/file_a" "$file_a" || { + log_kv component "$geo" + log_kv success 1 + log_kv current_version "$(file_mtime "$file_a")" + log_kv current_version_extra "$(file_mtime "$file_b")" + log_kv latest_version "$(file_mtime "$file_a")" + log_kv can_upgrade 0 + log_kv message 'Already up to date' + return 0 + } + geo_validate_download "$tmp_dir/file_b" "$file_b" || { + log_kv component "$geo" + log_kv success 1 + log_kv current_version "$(file_mtime "$file_a")" + log_kv current_version_extra "$(file_mtime "$file_b")" + log_kv latest_version "$(file_mtime "$file_a")" + log_kv can_upgrade 0 + log_kv message 'Already up to date' + return 0 + } + geo_safe_replace "$tmp_dir/file_a" "$file_a" "$tmp_dir/file_a.bak" || { + log_kv component "$geo" + log_kv success 0 + log_kv message 'Install failed' + return 0 + } + geo_safe_replace "$tmp_dir/file_b" "$file_b" "$tmp_dir/file_b.bak" || { + [ -f "$tmp_dir/file_a.bak" ] && cp -f "$tmp_dir/file_a.bak" "$file_a" 2>/dev/null || true + log_kv component "$geo" + log_kv success 0 + log_kv message 'Install failed' + return 0 + } + msg='Upgrade completed' + ;; + *) + log_kv component "$geo" + log_kv success 0 + log_kv message 'Unsupported component' + return 0 + ;; + esac + + log_kv component "$geo" + log_kv success 1 + log_kv current_version "$(file_mtime "$file_a")" + log_kv current_version_extra "$([ -n "${file_b:-}" ] && file_mtime "$file_b" || echo '')" + log_kv latest_version "$(file_mtime "$file_a")" + log_kv can_upgrade 0 + log_kv message "$msg" + return 0 +} + +get_xray_latest_tag() { + local location tag + + location="$(effective_url "$XRAY_RELEASE_PAGE")" || return 1 + tag="$(printf '%s' "$location" | sed -n 's#.*/tag/\(v[0-9][^/]*\)$#\1#p' | sed -n '1p')" + [ -n "$tag" ] || return 1 + printf '%s' "$tag" +} + +get_xray_latest_info() { + local tag version asset url arch + + arch="$(get_openwrt_arch)" + asset="$(map_xray_asset "$arch")" || return 2 + tag="$(get_xray_latest_tag)" || return 3 + version="$(trim_version "$tag")" + url="$(mirror_wrap_url "https://github.com/XTLS/Xray-core/releases/download/$tag/$asset")" + + [ -n "$tag" ] && [ -n "$version" ] && [ -n "$url" ] || return 4 + + log_kv arch "$arch" + log_kv asset "$asset" + log_kv latest_version "$version" + log_kv download_url "$url" + return 0 +} + +get_mihomo_latest_tag() { + local location tag + + location="$(effective_url "$MIHOMO_RELEASE_PAGE")" || return 1 + tag="$(printf '%s' "$location" | sed -n 's#.*/tag/\(v[0-9][^/]*\)$#\1#p' | sed -n '1p')" + [ -n "$tag" ] || return 1 + printf '%s' "$tag" +} + +map_mihomo_asset() { + local arch="$1" + local version="$2" + + case "$arch" in + x86_64*|amd64*) + printf 'mihomo-linux-amd64-compatible-v%s.gz' "$version" + ;; + i386*|i486*|i586*|i686*|x86*) + printf 'mihomo-linux-386-v%s.gz' "$version" + ;; + aarch64*|arm64*) + printf 'mihomo-linux-arm64-v%s.gz' "$version" + ;; + *armv7*|*cortex-a*|*neon*|*vfpv3*|*vfpv4*) + printf 'mihomo-linux-armv7-v%s.gz' "$version" + ;; + *armv6*|*arm1176*) + printf 'mihomo-linux-armv6-v%s.gz' "$version" + ;; + *armv5*|*arm926*|*xscale*) + printf 'mihomo-linux-armv5-v%s.gz' "$version" + ;; + mips64el*|mips64le*) + printf 'mihomo-linux-mips64le-v%s.gz' "$version" + ;; + mips64*) + printf 'mihomo-linux-mips64-v%s.gz' "$version" + ;; + mipsel*|mips32el*|mips32le*) + printf 'mihomo-linux-mipsle-softfloat-v%s.gz' "$version" + ;; + mips*) + printf 'mihomo-linux-mips-softfloat-v%s.gz' "$version" + ;; + riscv64*) + printf 'mihomo-linux-riscv64-v%s.gz' "$version" + ;; + loongarch64*|loong64*) + printf 'mihomo-linux-loong64-abi1-v%s.gz' "$version" + ;; + powerpc64le*|ppc64le*) + printf 'mihomo-linux-ppc64le-v%s.gz' "$version" + ;; + s390x*) + printf 'mihomo-linux-s390x-v%s.gz' "$version" + ;; + *) + return 1 + ;; + esac +} + +get_mihomo_latest_info() { + local arch tag version asset url + + arch="$(get_openwrt_arch)" + tag="$(get_mihomo_latest_tag)" || return 3 + version="$(trim_version "$tag")" + asset="$(map_mihomo_asset "$arch" "$version")" || return 2 + url="$(mirror_wrap_url "https://github.com/MetaCubeX/mihomo/releases/download/$tag/$asset")" + + log_kv arch "$arch" + log_kv asset "$asset" + log_kv latest_version "$version" + log_kv download_url "$url" + return 0 +} + +map_naiveproxy_linux_asset() { + case "$1" in + x86_64*|amd64*) + printf '%s' 'x64' + ;; + i386*|i486*|i586*|i686*|x86*) + printf '%s' 'x86' + ;; + aarch64*|arm64*) + printf '%s' 'arm64' + ;; + *armv7*|*cortex-a*|*neon*|*vfpv3*|*vfpv4*) + printf '%s' 'arm' + ;; + mips64el*|mips64le*) + printf '%s' 'mips64el' + ;; + mipsel*|mips32el*|mips32le*) + printf '%s' 'mipsel' + ;; + riscv64*) + printf '%s' 'riscv64' + ;; + loongarch64*|loong64*) + printf '%s' 'loong64' + ;; + *) + return 1 + ;; + esac +} + +normalize_naiveproxy_openwrt_arch() { + local arch="${1%%+*}" + printf '%s' "$arch" +} + +naiveproxy_openwrt_arch_candidates() { + local normalized + + normalized="$(normalize_naiveproxy_openwrt_arch "$1")" + printf '%s\n' "$normalized" + + case "$normalized" in + aarch64_*) + [ "$normalized" = "aarch64_generic" ] || printf '%s\n' 'aarch64_generic' + ;; + x86|i386|i486|i586|i686) + printf '%s\n' 'x86' + ;; + esac +} + +asset_list_has() { + local asset_list="$1" + local candidate="$2" + + printf '%s\n' "$asset_list" | grep -Fx "$candidate" >/dev/null 2>&1 +} + +select_naiveproxy_asset() { + local asset_list="$1" + local tag="$2" + local arch="$3" + local candidate linux_arch candidate_arch + + if is_openwrt_env; then + for candidate_arch in $(naiveproxy_openwrt_arch_candidates "$arch"); do + candidate="naiveproxy-${tag}-openwrt-${candidate_arch}-static.tar.xz" + if asset_list_has "$asset_list" "$candidate"; then + printf '%s' "$candidate" + return 0 + fi + + candidate="naiveproxy-${tag}-openwrt-${candidate_arch}.tar.xz" + if asset_list_has "$asset_list" "$candidate"; then + printf '%s' "$candidate" + return 0 + fi + done + fi + + linux_arch="$(map_naiveproxy_linux_asset "$arch" 2>/dev/null || true)" + if [ -n "$linux_arch" ]; then + candidate="naiveproxy-${tag}-linux-${linux_arch}.tar.xz" + if asset_list_has "$asset_list" "$candidate"; then + printf '%s' "$candidate" + return 0 + fi + fi + + return 1 +} + +get_naiveproxy_latest_info() { + local arch release_json tag version asset asset_list url + + arch="$(get_openwrt_arch)" + release_json="$(fetch_text "$NAIVEPROXY_RELEASE_API")" || return 3 + tag="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | sed -n '1p')" + [ -n "$tag" ] || return 3 + version="$(trim_version "$tag")" + asset_list="$(printf '%s\n' "$release_json" | sed -n 's/.*"name":[[:space:]]*"\([^"]*\.tar\.xz\)".*/\1/p')" + asset="$(select_naiveproxy_asset "$asset_list" "$tag" "$arch")" || return 4 + url="$(mirror_wrap_url "https://github.com/klzgrad/naiveproxy/releases/download/$tag/$asset")" + + log_kv arch "$arch" + log_kv asset "$asset" + log_kv latest_version "$version" + log_kv download_url "$url" + return 0 +} + +xray_info() { + local current installed latest_output latest_rc latest_version arch asset can_upgrade + + installed=0 + current="" + arch="$(get_openwrt_arch)" + asset="$(map_xray_asset "$arch" 2>/dev/null || true)" + if current="$(get_xray_current_version)" && [ -n "$current" ]; then + installed=1 + fi + + latest_output="$(get_xray_latest_info 2>/dev/null)" + latest_rc=$? + + log_kv component xray + log_kv installed "$installed" + log_kv current_version "$current" + log_kv arch "$arch" + log_kv asset "$asset" + + if [ $latest_rc -ne 0 ]; then + log_kv can_upgrade 0 + case "$latest_rc" in + 2) log_kv error 'unsupported_arch' ;; + 3) log_kv error 'fetch_failed' ;; + 4) log_kv error 'asset_not_found' ;; + *) log_kv error 'unknown_error' ;; + esac + return 0 + fi + + latest_version="$(printf '%s\n' "$latest_output" | sed -n 's/^latest_version=//p' | sed -n '1p')" + arch="$(printf '%s\n' "$latest_output" | sed -n 's/^arch=//p' | sed -n '1p')" + asset="$(printf '%s\n' "$latest_output" | sed -n 's/^asset=//p' | sed -n '1p')" + can_upgrade=0 + if [ -z "$current" ] || version_gt "$latest_version" "$current"; then + can_upgrade=1 + fi + + printf '%s\n' "$latest_output" | sed '/^download_url=/d' + log_kv can_upgrade "$can_upgrade" + log_kv error '' +} + +mihomo_info() { + local current installed latest_output latest_rc latest_version arch asset can_upgrade + + installed=0 + current="" + arch="$(get_openwrt_arch)" + if current="$(get_mihomo_current_version)" && [ -n "$current" ]; then + installed=1 + asset="$(map_mihomo_asset "$arch" "$current" 2>/dev/null || true)" + else + asset="" + fi + + latest_output="$(get_mihomo_latest_info 2>/dev/null)" + latest_rc=$? + + log_kv component mihomo + log_kv installed "$installed" + log_kv current_version "$current" + log_kv arch "$arch" + log_kv asset "$asset" + + if [ $latest_rc -ne 0 ]; then + log_kv can_upgrade 0 + case "$latest_rc" in + 2) log_kv error 'unsupported_arch' ;; + 3) log_kv error 'fetch_failed' ;; + 4) log_kv error 'asset_not_found' ;; + *) log_kv error 'unknown_error' ;; + esac + return 0 + fi + + latest_version="$(printf '%s\n' "$latest_output" | sed -n 's/^latest_version=//p' | sed -n '1p')" + arch="$(printf '%s\n' "$latest_output" | sed -n 's/^arch=//p' | sed -n '1p')" + asset="$(printf '%s\n' "$latest_output" | sed -n 's/^asset=//p' | sed -n '1p')" + can_upgrade=0 + if [ -z "$current" ] || version_gt "$latest_version" "$current"; then + can_upgrade=1 + fi + + printf '%s\n' "$latest_output" | sed '/^download_url=/d' + log_kv can_upgrade "$can_upgrade" + log_kv error '' +} + +naiveproxy_info() { + local current installed latest_output latest_rc latest_version arch asset can_upgrade + + installed=0 + current="" + arch="$(get_openwrt_arch)" + if current="$(get_naiveproxy_current_version)" && [ -n "$current" ]; then + installed=1 + fi + + latest_output="$(get_naiveproxy_latest_info 2>/dev/null)" + latest_rc=$? + + log_kv component naiveproxy + log_kv installed "$installed" + log_kv current_version "$current" + log_kv arch "$arch" + log_kv asset '' + + if [ $latest_rc -ne 0 ]; then + log_kv can_upgrade 0 + case "$latest_rc" in + 3) log_kv error 'fetch_failed' ;; + 4) log_kv error 'asset_not_found' ;; + *) log_kv error 'unknown_error' ;; + esac + return 0 + fi + + latest_version="$(printf '%s\n' "$latest_output" | sed -n 's/^latest_version=//p' | sed -n '1p')" + arch="$(printf '%s\n' "$latest_output" | sed -n 's/^arch=//p' | sed -n '1p')" + asset="$(printf '%s\n' "$latest_output" | sed -n 's/^asset=//p' | sed -n '1p')" + can_upgrade=0 + if [ -z "$current" ]; then + can_upgrade=1 + elif ! naiveproxy_versions_equal "$latest_version" "$current" && version_gt "$latest_version" "$current"; then + can_upgrade=1 + fi + + printf '%s\n' "$latest_output" | sed '/^download_url=/d' + log_kv can_upgrade "$can_upgrade" + log_kv error '' +} + +xray_local_info() { + local current installed arch asset + + installed=0 + current="" + arch="$(get_openwrt_arch)" + asset="$(map_xray_asset "$arch" 2>/dev/null || true)" + if current="$(get_xray_current_version)" && [ -n "$current" ]; then + installed=1 + fi + + log_kv component xray + log_kv installed "$installed" + log_kv current_version "$current" + log_kv latest_version '' + log_kv arch "$arch" + log_kv asset "$asset" + log_kv can_upgrade 0 + log_kv error '' +} + +mihomo_local_info() { + local current installed arch asset + + installed=0 + current="" + arch="$(get_openwrt_arch)" + if current="$(get_mihomo_current_version)" && [ -n "$current" ]; then + installed=1 + asset="$(map_mihomo_asset "$arch" "$current" 2>/dev/null || true)" + else + asset="" + fi + + log_kv component mihomo + log_kv installed "$installed" + log_kv current_version "$current" + log_kv latest_version '' + log_kv arch "$arch" + log_kv asset "$asset" + log_kv can_upgrade 0 + log_kv error '' +} + +naiveproxy_local_info() { + local current installed arch + + installed=0 + current="" + arch="$(get_openwrt_arch)" + if current="$(get_naiveproxy_current_version)" && [ -n "$current" ]; then + installed=1 + fi + + log_kv component naiveproxy + log_kv installed "$installed" + log_kv current_version "$current" + log_kv latest_version '' + log_kv arch "$arch" + log_kv asset '' + log_kv can_upgrade 0 + log_kv error '' +} + +xray_upgrade() { + local latest_output latest_rc latest_version download_url tmp_dir zip_file unzip_cmd backup_file current_before current_after + + latest_output="$(get_xray_latest_info 2>/dev/null)" + latest_rc=$? + if [ $latest_rc -ne 0 ]; then + log_kv success 0 + case "$latest_rc" in + 2) log_kv message 'Unsupported ARCH' ;; + 3) log_kv message 'Failed to fetch release metadata' ;; + 4) log_kv message 'Matching release asset not found' ;; + *) log_kv message 'Unknown error' ;; + esac + return 0 + fi + + if ! unzip_cmd="$(select_unzip_cmd)"; then + log_kv success 0 + log_kv message 'Missing unzip support' + return 0 + fi + + latest_version="$(printf '%s\n' "$latest_output" | sed -n 's/^latest_version=//p' | sed -n '1p')" + download_url="$(printf '%s\n' "$latest_output" | sed -n 's/^download_url=//p' | sed -n '1p')" + current_before="$(get_xray_current_version 2>/dev/null || true)" + if [ -n "$current_before" ] && ! version_gt "$latest_version" "$current_before"; then + log_kv success 1 + log_kv previous_version "$current_before" + log_kv current_version "$current_before" + log_kv latest_version "$latest_version" + log_kv message 'Already up to date' + return 0 + fi + + tmp_dir="$(mktemp -d /tmp/ssrplus-xray.XXXXXX)" + if [ -z "$tmp_dir" ] || [ ! -d "$tmp_dir" ]; then + log_kv success 0 + log_kv message 'Failed to create temp directory' + return 0 + fi + + zip_file="$tmp_dir/xray.zip" + backup_file="$tmp_dir/xray.backup" + + trap "rm -rf '$tmp_dir'" EXIT INT TERM + + if ! download_file "$download_url" "$zip_file"; then + log_kv success 0 + log_kv message 'Download failed' + return 0 + fi + + if ! sh -c "$unzip_cmd \"$zip_file\" -d \"$tmp_dir\" >/dev/null 2>&1"; then + log_kv success 0 + log_kv message 'Extract failed' + return 0 + fi + + if [ ! -f "$tmp_dir/xray" ]; then + log_kv success 0 + log_kv message 'xray binary not found in archive' + return 0 + fi + + chmod 0755 "$tmp_dir/xray" || true + if [ -x "$XRAY_BINARY" ]; then + cp -fp "$XRAY_BINARY" "$backup_file" 2>/dev/null || true + fi + + if ! cp -f "$tmp_dir/xray" "$XRAY_BINARY"; then + if [ -f "$backup_file" ]; then + cp -f "$backup_file" "$XRAY_BINARY" 2>/dev/null || true + fi + log_kv success 0 + log_kv message 'Install failed' + return 0 + fi + + chmod 0755 "$XRAY_BINARY" || true + current_after="$(get_xray_current_version 2>/dev/null || true)" + if [ -z "$current_after" ]; then + if [ -f "$backup_file" ]; then + cp -f "$backup_file" "$XRAY_BINARY" 2>/dev/null || true + chmod 0755 "$XRAY_BINARY" || true + fi + log_kv success 0 + log_kv message 'Installed binary failed to run' + return 0 + fi + + if [ -x /etc/init.d/shadowsocksr ]; then + /etc/init.d/shadowsocksr restart >/dev/null 2>&1 || true + fi + + log_kv success 1 + log_kv previous_version "$current_before" + log_kv current_version "$current_after" + log_kv latest_version "$latest_version" + log_kv message 'Upgrade completed' + return 0 +} + +mihomo_upgrade() { + local latest_output latest_rc latest_version download_url tmp_dir gz_file gzip_cmd backup_file current_before current_after target_binary extracted_binary + + latest_output="$(get_mihomo_latest_info 2>/dev/null)" + latest_rc=$? + if [ $latest_rc -ne 0 ]; then + log_kv success 0 + case "$latest_rc" in + 2) log_kv message 'Unsupported ARCH' ;; + 3) log_kv message 'Failed to fetch release metadata' ;; + 4) log_kv message 'Matching release asset not found' ;; + *) log_kv message 'Unknown error' ;; + esac + return 0 + fi + + if ! gzip_cmd="$(select_gzip_cmd)"; then + log_kv success 0 + log_kv message 'Missing gzip support' + return 0 + fi + + latest_version="$(printf '%s\n' "$latest_output" | sed -n 's/^latest_version=//p' | sed -n '1p')" + download_url="$(printf '%s\n' "$latest_output" | sed -n 's/^download_url=//p' | sed -n '1p')" + current_before="$(get_mihomo_current_version 2>/dev/null || true)" + if [ -n "$current_before" ] && ! version_gt "$latest_version" "$current_before"; then + log_kv success 1 + log_kv previous_version "$current_before" + log_kv current_version "$current_before" + log_kv latest_version "$latest_version" + log_kv message 'Already up to date' + return 0 + fi + + target_binary="$(find_mihomo_binary 2>/dev/null || true)" + [ -n "$target_binary" ] || target_binary="$MIHOMO_BINARY" + + tmp_dir="$(mktemp -d /tmp/ssrplus-mihomo.XXXXXX)" + if [ -z "$tmp_dir" ] || [ ! -d "$tmp_dir" ]; then + log_kv success 0 + log_kv message 'Failed to create temp directory' + return 0 + fi + + gz_file="$tmp_dir/mihomo.gz" + backup_file="$tmp_dir/mihomo.backup" + extracted_binary="$tmp_dir/mihomo" + + trap "rm -rf '$tmp_dir'" EXIT INT TERM + + if ! download_file "$download_url" "$gz_file"; then + log_kv success 0 + log_kv message 'Download failed' + return 0 + fi + + if ! sh -c "$gzip_cmd \"$gz_file\" > \"$extracted_binary\""; then + log_kv success 0 + log_kv message 'Extract failed' + return 0 + fi + + if [ ! -s "$extracted_binary" ]; then + log_kv success 0 + log_kv message 'mihomo binary not found in archive' + return 0 + fi + + mkdir -p "$(dirname "$target_binary")" || true + chmod 0755 "$extracted_binary" || true + if [ -x "$target_binary" ]; then + cp -fp "$target_binary" "$backup_file" 2>/dev/null || true + fi + + if ! cp -f "$extracted_binary" "$target_binary"; then + if [ -f "$backup_file" ]; then + cp -f "$backup_file" "$target_binary" 2>/dev/null || true + fi + log_kv success 0 + log_kv message 'Install failed' + return 0 + fi + + chmod 0755 "$target_binary" || true + if [ "$target_binary" != "$MIHOMO_BINARY" ] && [ ! -x "$MIHOMO_BINARY" ]; then + ln -sf "$target_binary" "$MIHOMO_BINARY" 2>/dev/null || true + fi + + current_after="$(get_mihomo_current_version 2>/dev/null || true)" + if [ -z "$current_after" ]; then + if [ -f "$backup_file" ]; then + cp -f "$backup_file" "$target_binary" 2>/dev/null || true + chmod 0755 "$target_binary" || true + fi + log_kv success 0 + log_kv message 'Installed binary failed to run' + return 0 + fi + + if [ -x /etc/init.d/shadowsocksr ]; then + /etc/init.d/shadowsocksr restart >/dev/null 2>&1 || true + fi + + log_kv success 1 + log_kv previous_version "$current_before" + log_kv current_version "$current_after" + log_kv latest_version "$latest_version" + log_kv message 'Upgrade completed' + return 0 +} + +naiveproxy_upgrade() { + local latest_output latest_rc latest_version download_url tmp_dir archive_file xz_cmd tar_cmd backup_file current_before current_after target_binary extracted_binary + + latest_output="$(get_naiveproxy_latest_info 2>/dev/null)" + latest_rc=$? + if [ $latest_rc -ne 0 ]; then + log_kv success 0 + case "$latest_rc" in + 3) log_kv message 'Failed to fetch release metadata' ;; + 4) log_kv message 'Matching release asset not found' ;; + *) log_kv message 'Unknown error' ;; + esac + return 0 + fi + + if ! xz_cmd="$(select_xz_cmd)"; then + log_kv success 0 + log_kv message 'Missing xz support' + return 0 + fi + + if ! tar_cmd="$(select_tar_cmd)"; then + log_kv success 0 + log_kv message 'Extract failed' + return 0 + fi + + latest_version="$(printf '%s\n' "$latest_output" | sed -n 's/^latest_version=//p' | sed -n '1p')" + download_url="$(printf '%s\n' "$latest_output" | sed -n 's/^download_url=//p' | sed -n '1p')" + current_before="$(get_naiveproxy_current_version 2>/dev/null || true)" + if [ -n "$current_before" ] && ! version_gt "$latest_version" "$current_before"; then + log_kv success 1 + log_kv previous_version "$current_before" + log_kv current_version "$current_before" + log_kv latest_version "$latest_version" + log_kv message 'Already up to date' + return 0 + fi + + target_binary="$(find_naiveproxy_binary 2>/dev/null || true)" + [ -n "$target_binary" ] || target_binary="$NAIVEPROXY_BINARY" + + tmp_dir="$(mktemp -d /tmp/ssrplus-naiveproxy.XXXXXX)" + if [ -z "$tmp_dir" ] || [ ! -d "$tmp_dir" ]; then + log_kv success 0 + log_kv message 'Failed to create temp directory' + return 0 + fi + + archive_file="$tmp_dir/naiveproxy.tar.xz" + backup_file="$tmp_dir/naive.backup" + extracted_binary="" + + trap "rm -rf '$tmp_dir'" EXIT INT TERM + + if ! download_file "$download_url" "$archive_file"; then + log_kv success 0 + log_kv message 'Download failed' + return 0 + fi + + mkdir -p "$tmp_dir/extract" || { + log_kv success 0 + log_kv message 'Failed to create temp directory' + return 0 + } + + if ! sh -c "cd \"$tmp_dir/extract\" && $xz_cmd \"$archive_file\" | $tar_cmd -xf - >/dev/null 2>&1"; then + log_kv success 0 + log_kv message 'Extract failed' + return 0 + fi + + extracted_binary="$(find "$tmp_dir/extract" -type f -name naive | sed -n '1p')" + if [ -z "$extracted_binary" ] || [ ! -s "$extracted_binary" ]; then + log_kv success 0 + log_kv message 'naive binary not found in archive' + return 0 + fi + + mkdir -p "$(dirname "$target_binary")" || true + chmod 0755 "$extracted_binary" || true + if [ -x "$target_binary" ]; then + cp -fp "$target_binary" "$backup_file" 2>/dev/null || true + fi + + if ! cp -f "$extracted_binary" "$target_binary"; then + if [ -f "$backup_file" ]; then + cp -f "$backup_file" "$target_binary" 2>/dev/null || true + fi + log_kv success 0 + log_kv message 'Install failed' + return 0 + fi + + chmod 0755 "$target_binary" || true + if [ "$target_binary" != "$NAIVEPROXY_BINARY" ] && [ ! -x "$NAIVEPROXY_BINARY" ]; then + ln -sf "$target_binary" "$NAIVEPROXY_BINARY" 2>/dev/null || true + fi + + current_after="$(get_naiveproxy_current_version 2>/dev/null || true)" + if [ -z "$current_after" ]; then + if [ -f "$backup_file" ]; then + cp -f "$backup_file" "$target_binary" 2>/dev/null || true + chmod 0755 "$target_binary" || true + fi + log_kv success 0 + log_kv message 'Installed binary failed to run' + return 0 + fi + + if [ -x /etc/init.d/shadowsocksr ]; then + /etc/init.d/shadowsocksr restart >/dev/null 2>&1 || true + fi + + log_kv success 1 + log_kv previous_version "$current_before" + log_kv current_version "$current_after" + log_kv latest_version "$latest_version" + log_kv message 'Upgrade completed' + return 0 +} + +case "${1:-}" in + xray_info) + xray_info + ;; + xray_local_info) + xray_local_info + ;; + xray_upgrade) + xray_upgrade + ;; + mihomo_info) + mihomo_info + ;; + mihomo_local_info) + mihomo_local_info + ;; + mihomo_upgrade) + mihomo_upgrade + ;; + naiveproxy_info) + naiveproxy_info + ;; + naiveproxy_local_info) + naiveproxy_local_info + ;; + naiveproxy_upgrade) + naiveproxy_upgrade + ;; + country_mmdb_info) + geo_local_info country_mmdb + ;; + country_mmdb_local_info) + geo_local_info country_mmdb + ;; + country_mmdb_upgrade) + geo_upgrade country_mmdb + ;; + geosite_info) + geo_local_info geosite + ;; + geosite_local_info) + geo_local_info geosite + ;; + geosite_upgrade) + geo_upgrade geosite + ;; + v2ray_geo_info) + geo_local_info v2ray_geo + ;; + v2ray_geo_local_info) + geo_local_info v2ray_geo + ;; + v2ray_geo_upgrade) + geo_upgrade v2ray_geo + ;; + *) + log_kv success 0 + log_kv message 'Usage: update_components.sh xray_info|xray_local_info|xray_upgrade|mihomo_info|mihomo_local_info|mihomo_upgrade|naiveproxy_info|naiveproxy_local_info|naiveproxy_upgrade|country_mmdb_info|country_mmdb_local_info|country_mmdb_upgrade|geosite_info|geosite_local_info|geosite_upgrade|v2ray_geo_info|v2ray_geo_local_info|v2ray_geo_upgrade' + return 1 2>/dev/null || exit 1 + ;; +esac diff --git a/luci-theme-aurora/.dev/src/media/components/_button.css b/luci-theme-aurora/.dev/src/media/components/_button.css index bfe4a3ed3..dc7aadb9c 100644 --- a/luci-theme-aurora/.dev/src/media/components/_button.css +++ b/luci-theme-aurora/.dev/src/media/components/_button.css @@ -1,4 +1,7 @@ button, +input[type="button"], +input[type="submit"], +input[type="reset"], .btn, .cbi-button { @apply inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-2xl border px-3 py-1.5 text-sm font-medium antialiased shadow-sm max-md:text-base max-md:font-medium; diff --git a/luci-theme-aurora/Makefile b/luci-theme-aurora/Makefile index 5ffb8eb47..cc2ccc5ad 100644 --- a/luci-theme-aurora/Makefile +++ b/luci-theme-aurora/Makefile @@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS) LUCI_DEPENDS:=+luci-base -PKG_VERSION:=0.12.0 -PKG_RELEASE:=17 +PKG_VERSION:=0.12.1 +PKG_RELEASE:=18 PKG_LICENSE:=Apache-2.0 LUCI_MINIFY_CSS:= diff --git a/luci-theme-aurora/htdocs/luci-static/aurora/main.css b/luci-theme-aurora/htdocs/luci-static/aurora/main.css index 49d411c20..489e24642 100644 --- a/luci-theme-aurora/htdocs/luci-static/aurora/main.css +++ b/luci-theme-aurora/htdocs/luci-static/aurora/main.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ -@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-font-weight:initial;--tw-duration:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-space-y-reverse:0;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-2xl:calc(var(--radius-base)*2);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{scrollbar-color:initial;scrollbar-width:initial}.collapse{visibility:collapse}.fixed{position:fixed}.static{position:static}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.scrollbar::-webkit-scrollbar-track{background-color:var(--scrollbar-track);border-radius:var(--scrollbar-track-radius)}.scrollbar::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);border-radius:var(--scrollbar-thumb-radius)}.scrollbar::-webkit-scrollbar-corner{background-color:var(--scrollbar-corner);border-radius:var(--scrollbar-corner-radius)}.scrollbar{scrollbar-width:auto;scrollbar-color:var(--scrollbar-thumb,initial)var(--scrollbar-track,initial)}.scrollbar::-webkit-scrollbar{width:var(--scrollbar-width,16px);height:var(--scrollbar-height,16px);display:block}.hidden{display:none}.table{display:table}.rounded{border-radius:calc(var(--radius-base)*.5)}.underline{text-decoration-line:underline}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--background:#f1f5f9;--foreground:#0f172b;--primary:#46a3d1;--primary-foreground:#fff;--secondary:#e2e8f0;--secondary-foreground:#314158;--muted:#f5f5f5;--muted-foreground:#45556c;--destructive:#ffe2df;--destructive-foreground:#6c1517;--accent:#ee343b;--accent-foreground:#fff1f0;--default:#f5f5f5;--default-foreground:#171717;--info:#d3f1ff;--info-foreground:#003f60;--warning:#fbeec9;--warning-foreground:#582f02;--success:#cff6e0;--success-foreground:#003d2a;--error:#ffe2df;--error-foreground:#6c1517;--border:#e4e4e4;--link:#ec6cff;--input-checked:#fff;--label-surface:oklch(from var(--panel-bg)calc(l - .02)c h);--login-aurora-1:#00c23d;--login-aurora-2:#00a6a0;--login-aurora-3:#4266ff;--login-aurora-4:#b93ef3;--terminal-bg:#1a2029;--terminal-foreground:#fff;--overlay-base:#000;--progress-bar-start:#46a3d1;--progress-bar-end:#4fc3c7;--header-bg:oklch(from var(--background)l c h);--header-interactive:oklch(from var(--header-bg)calc(l - .12)c h);--page-bg:#fff;--panel-bg:#fff;--tooltip-bg:oklch(from var(--page-bg)calc(l - .015)c h);--font-sans:"Lato",ui-sans-serif,system-ui,sans-serif;--font-mono:ui-monospace,"SF Mono",Menlo,Monaco,Consolas,monospace;--spacing:.25rem;--container-max-width:80rem;--radius-base:.5rem}@supports (color:lab(0% 0 0)){:root{--background:lab(96.286% -.852436 -2.46847);--foreground:lab(7.78673% 1.82345 -15.0537);--primary:lab(63.0437% -17.6746 -32.0381);--primary-foreground:lab(100% 0 0);--secondary:lab(91.7353% -.998765 -4.76968);--secondary-foreground:lab(26.9569% -1.47016 -15.6993);--muted:lab(96.52% -.0000298023 .0000119209);--muted-foreground:lab(35.5623% -1.74978 -15.4316);--destructive:lab(92.5749% 15.3735 8.32583);--destructive-foreground:lab(23.385% 37.9736 23.5817);--accent:lab(53.6853% 69.6775 43.7724);--accent-foreground:lab(96.3359% 6.12152 3.29586);--default:lab(96.52% -.0000298023 .0000119209);--default-foreground:lab(7.78201% -.0000149012 0);--info:lab(93.2141% -10.1735 -14.3943);--info-foreground:lab(24.4811% -7.8918 -24.9024);--warning:lab(94.421% .545532 19.5962);--warning-foreground:lab(24.2528% 17.0621 33.1082);--success:lab(93.715% -16.1219 6.39679);--success-foreground:lab(22.3041% -31.6825 8.84454);--error:lab(92.5749% 15.3735 8.32583);--error-foreground:lab(23.385% 37.9736 23.5817);--border:lab(90.72% .0000298023 -.0000119209);--link:lab(66.1178% 66.0652 -52.4733);--input-checked:lab(100% 0 0);--login-aurora-1:lab(68.2738% -77.861 60.3677);--login-aurora-2:lab(61.8807% -76.0735 -14.3835);--login-aurora-3:lab(47.9708% 31.2682 -84.9353);--login-aurora-4:lab(51.406% 68.7947 -68.8447);--terminal-bg:lab(11.7506% -.5823 -7.27103);--terminal-foreground:lab(100% 0 0);--overlay-base:lab(0% 0 0);--progress-bar-start:lab(63.0437% -17.6746 -32.0381);--progress-bar-end:lab(72.4424% -32.9195 -12.3464);--page-bg:lab(100% 0 0);--panel-bg:lab(100% 0 0)}}[data-darkmode=true]{--background:#003c38;--foreground:#f1f5f9;--primary:#00958e;--primary-foreground:#fff;--secondary:#314158;--secondary-foreground:#e5e7eb;--muted:#38414f;--muted-foreground:#90a1b9;--destructive:#460809;--destructive-foreground:#ffc6c0;--accent:#6c1517;--accent-foreground:#ffc6c0;--default:#27272a80;--default-foreground:#f9f9ff;--info:#024a7080;--info-foreground:#b0e0f8;--warning:#7b330680;--warning-foreground:#fee685;--success:#004e3b80;--success-foreground:#aff8d0;--error:#8b083680;--error-foreground:#ffc6c0;--border:#364358;--link:#a685ff;--input-checked:#fff;--label-surface:oklch(from var(--panel-bg)calc(l + .04)c h);--login-aurora-1:#00d847;--login-aurora-2:#00b7b3;--login-aurora-3:#5279ff;--login-aurora-4:#c854ff;--terminal-bg:#0a121f;--terminal-foreground:#fff;--overlay-base:#000;--progress-bar-start:#065f46;--progress-bar-end:#009b94;--header-bg:oklch(from var(--page-bg)l c h);--header-interactive:oklch(from var(--header-bg)calc(l + .24)c h);--page-bg:#101828;--panel-bg:#1d293d;--tooltip-bg:oklch(from var(--page-bg)calc(l + .06)c h)}@supports (color:lab(0% 0 0)){[data-darkmode=true]{--background:lab(21.8032% -20.1655 -3.35968);--foreground:lab(96.286% -.852436 -2.46847);--primary:lab(55.0978% -44.5567 -7.62978);--primary-foreground:lab(100% 0 0);--secondary:lab(26.9569% -1.47016 -15.6993);--secondary-foreground:lab(91.6229% -.159115 -2.26791);--muted:lab(27.1597% -.911355 -9.48523);--muted-foreground:lab(65.5349% -2.25151 -14.5072);--destructive:lab(13.003% 29.04 16.7519);--destructive-foreground:lab(84.7556% 43.3341 25.2722);--accent:lab(23.385% 37.9736 23.5817);--accent-foreground:lab(84.7347% 43.595 24.3042);--default:lab(15.7305% .613764 -2.16959/.5);--default-foreground:lab(98.1732% .982702 -3.63715);--info:lab(29.1959% -8.34689 -28.2453/.5);--info-foreground:lab(86.2797% -12.0086 -17.1909);--warning:lab(31.2288% 30.2627 40.0378/.5);--warning-foreground:lab(91.7203% -.505269 49.9084);--success:lab(28.8637% -26.9249 5.45986/.5);--success-foreground:lab(91.9265% -29.2737 11.5846);--error:lab(29.7104% 51.514 12.6253/.5);--error-foreground:lab(84.7347% 43.595 24.3042);--border:lab(27.8781% -.856712 -14.804);--link:lab(62.8239% 34.9159 -60.0512);--input-checked:lab(100% 0 0);--login-aurora-1:lab(75.4735% -83.8096 64.8452);--login-aurora-2:lab(67.7176% -77.9628 -17.8815);--login-aurora-3:lab(53.7474% 31.0421 -88.4899);--login-aurora-4:lab(57.0971% 71.0297 -71.531);--terminal-bg:lab(5.13221% -.0256523 -9.94164);--terminal-foreground:lab(100% 0 0);--overlay-base:lab(0% 0 0);--progress-bar-start:lab(35.2336% -29.9851 7.30804);--progress-bar-end:lab(57.6867% -49.5425 -9.29472);--page-bg:lab(8.11897% .811279 -12.254);--panel-bg:lab(16.132% -.318035 -14.6672)}}:not(html)::-webkit-scrollbar-track{background-color:var(--scrollbar-track);border-radius:var(--scrollbar-track-radius)}:not(html)::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);border-radius:var(--scrollbar-thumb-radius)}:not(html)::-webkit-scrollbar-corner{background-color:var(--scrollbar-corner);border-radius:var(--scrollbar-corner-radius)}:not(html){scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb,initial)var(--scrollbar-track,initial)}:not(html)::-webkit-scrollbar{width:8px;height:8px;display:block}:not(html){--scrollbar-thumb:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:not(html){--scrollbar-thumb:color-mix(in oklab,var(--foreground)70%,transparent)}}:not(html){--scrollbar-thumb-radius:9999px;--scrollbar-track:transparent}html::-webkit-scrollbar-track{background-color:var(--scrollbar-track);border-radius:var(--scrollbar-track-radius)}html::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);border-radius:var(--scrollbar-thumb-radius)}html::-webkit-scrollbar-corner{background-color:var(--scrollbar-corner);border-radius:var(--scrollbar-corner-radius)}html{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb,initial)var(--scrollbar-track,initial)}html::-webkit-scrollbar{width:8px;height:8px;display:block}html{--scrollbar-thumb:var(--foreground)}@supports (color:color-mix(in lab, red, red)){html{--scrollbar-thumb:color-mix(in oklab,var(--foreground)70%,transparent)}}html{--scrollbar-thumb-radius:9999px;--scrollbar-track:var(--page-bg);background-color:var(--background);height:100%;font-family:var(--font-sans);position:relative}html body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--foreground);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;background-color:#0000;flex-direction:column;transition-duration:.3s;display:flex;position:relative}html body.modal-overlay-active{height:100vh;overflow:hidden}h1,h2,h3,h4,h5,h6{font-family:var(--font-sans);--tw-leading:var(--leading-tight);line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--foreground)}h1{margin-bottom:calc(var(--spacing)*4);padding-left:calc(var(--spacing)*6);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}@media not all and (min-width:48rem){h1{padding-left:calc(var(--spacing)*4);font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}}h2{margin-bottom:calc(var(--spacing)*4);padding-left:calc(var(--spacing)*5);font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}@media not all and (min-width:48rem){h2{margin-bottom:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*4);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}h3{margin-bottom:calc(var(--spacing)*4);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}@media not all and (min-width:48rem){h3{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}h4{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}@media not all and (min-width:48rem){h4{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}h5{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}@media not all and (min-width:48rem){h5{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}h6{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}@media not all and (min-width:48rem){h6{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}hr{border-style:var(--tw-border-style);border-width:0}strong{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}abbr{cursor:help}p{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--default-foreground)}pre{border-radius:calc(var(--radius-base)*1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--secondary);display:block}@supports (color:color-mix(in lab, red, red)){pre{border-color:color-mix(in oklab,var(--secondary)60%,transparent)}}pre{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){pre{background-color:color-mix(in oklab,var(--secondary)60%,transparent)}}pre{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);overflow-wrap:break-word;white-space:pre-wrap;color:var(--secondary-foreground)}code{border-radius:calc(var(--radius-base)*.5);background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){code{background-color:color-mix(in oklab,var(--secondary)80%,transparent)}}code{padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1);font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--secondary-foreground)}a{color:var(--link);text-decoration-line:none}@media (hover:hover){a:hover{text-decoration-line:underline}}var{font-family:var(--font-mono);color:var(--link)}small{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--default-foreground)}em{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--muted-foreground);font-style:italic}header{top:calc(var(--spacing)*0);z-index:60;margin-bottom:calc(var(--spacing)*2);background-color:var(--header-bg);position:sticky}header .header-content{height:calc(var(--spacing)*14);padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*3);justify-content:space-between;align-items:center;display:flex;position:relative}@media not all and (min-width:48rem){header .header-content{padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2)}}[data-nav-type=mega-menu] :is(header) .desktop-menu-container{pointer-events:none;inset-inline:calc(var(--spacing)*0);top:calc(var(--spacing)*0);z-index:30;height:calc(var(--spacing)*0);background-color:var(--header-bg);width:100%;position:absolute;overflow:hidden}@supports (color:color-mix(in lab, red, red)){[data-nav-type=mega-menu] :is(header) .desktop-menu-container{background-color:color-mix(in oklab,var(--header-bg)40%,transparent)}}[data-nav-type=mega-menu] :is(header) .desktop-menu-container{opacity:0;--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;--tw-ease:var(--ease-in-out);transition-duration:.15s;transition-timing-function:var(--ease-in-out)}@media not all and (min-width:48rem){[data-nav-type=mega-menu] :is(header) .desktop-menu-container{display:none}}[data-nav-type=mega-menu] :is(header) .desktop-menu-container:where([data-darkmode=true],[data-darkmode=true] *){background-color:var(--header-bg)}@supports (color:color-mix(in lab, red, red)){[data-nav-type=mega-menu] :is(header) .desktop-menu-container:where([data-darkmode=true],[data-darkmode=true] *){background-color:color-mix(in oklab,var(--header-bg)90%,transparent)}}[data-nav-type=mega-menu] :is(header) .desktop-menu-container.active{pointer-events:auto;height:var(--mega-menu-height,0);opacity:1}header .brand{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--foreground);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;text-decoration-line:none;transition-duration:.2s;display:inline-block}@media (hover:hover){header .brand:hover{--tw-translate-y:calc(var(--spacing)*-.5);translate:var(--tw-translate-x)var(--tw-translate-y);color:var(--primary)}}@media not all and (min-width:48rem){header .brand{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));flex:1;order:1}}header .nav{z-index:60;margin:calc(var(--spacing)*0);--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);align-items:center;gap:calc(var(--spacing)*1);padding:calc(var(--spacing)*0);list-style-type:none;display:flex;position:absolute;left:50%}@media not all and (min-width:48rem){header .nav{display:none}}header .nav>li{position:relative}header .nav>li .menu{border-radius:calc(var(--radius-base)*1.5);padding-inline:calc(var(--spacing)*3.5);padding-block:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--foreground);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;text-decoration-line:none;transition-duration:.15s;display:block}header .nav>li .menu.menu-active{color:var(--foreground)}[data-nav-type=mega-menu] header .nav>li .menu.menu-active{text-underline-offset:4px;text-decoration-line:underline;text-decoration-thickness:2px}[data-nav-type=boxed-dropdown] header .nav>li .menu.menu-active{background-color:var(--header-interactive)}header .nav>li .desktop-nav{pointer-events:none;left:calc(var(--spacing)*0);z-index:40;padding-inline:calc(var(--spacing)*0);padding-top:calc(var(--spacing)*0);opacity:0;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;transition-duration:.15s}[data-nav-type=mega-menu] :is(header .nav>li .desktop-nav){top:calc(var(--spacing)*14);--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);padding-bottom:calc(var(--spacing)*6);position:fixed;left:50%}[data-nav-type=boxed-dropdown] :is(header .nav>li .desktop-nav){margin-top:calc(var(--spacing)*2);min-width:calc(var(--spacing)*48);position:absolute;top:100%}header .nav>li .desktop-nav.active{pointer-events:auto;opacity:1}[data-nav-type=mega-menu] :is(header .nav>li .desktop-nav .desktop-nav-list){column-gap:calc(var(--spacing)*4);row-gap:calc(var(--spacing)*2);grid-template-columns:repeat(4,minmax(0,1fr));width:max-content;display:grid}[data-nav-type=boxed-dropdown] :is(header .nav>li .desktop-nav .desktop-nav-list){row-gap:calc(var(--spacing)*1);border-radius:calc(var(--radius-base)*3);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:color-mix(in oklab,var(--header-bg)90%,transparent);padding-block:calc(var(--spacing)*2);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-md));--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-direction:column;transition-duration:.2s;display:flex}header .nav>li .desktop-nav .desktop-nav-list>li{margin:calc(var(--spacing)*0);list-style-type:none}header .nav>li .desktop-nav .desktop-nav-list>li>a{padding-inline:calc(var(--spacing)*4);white-space:nowrap;color:var(--foreground);text-decoration-line:none;display:block}[data-nav-type=mega-menu] :is(header .nav>li .desktop-nav .desktop-nav-list>li>a){padding-block:calc(var(--spacing)*3)}@media (hover:hover){[data-nav-type=mega-menu] :is(header .nav>li .desktop-nav .desktop-nav-list>li>a):hover{text-underline-offset:4px;text-decoration-line:underline;text-decoration-thickness:2px}}[data-nav-type=boxed-dropdown] :is(header .nav>li .desktop-nav .desktop-nav-list>li>a){margin-inline:calc(var(--spacing)*2);border-radius:calc(var(--radius-base)*1.5);padding-block:calc(var(--spacing)*2)}@media (hover:hover){[data-nav-type=boxed-dropdown] :is(header .nav>li .desktop-nav .desktop-nav-list>li>a):hover{background-color:var(--header-interactive)}}@media not all and (min-width:48rem){header .mobile-controls{flex-shrink:0;order:9999;align-items:center;display:flex}}@media (min-width:48rem){header .mobile-controls{display:none}}@media not all and (min-width:48rem){header .mobile-controls .mobile-menu-toggle{margin-left:calc(var(--spacing)*5);cursor:pointer;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}@media (hover:hover){header .mobile-controls .mobile-menu-toggle:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}}header .mobile-controls .mobile-menu-toggle:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}}header .mobile-controls .mobile-menu-toggle svg{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}@media not all and (min-width:48rem){header .mobile-controls .mobile-menu-toggle svg{color:var(--foreground);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}header .mobile-controls .mobile-menu-toggle.active svg{rotate:180deg}}header #indicators{gap:calc(var(--spacing)*5);flex-direction:row-reverse;flex-shrink:0;display:flex}@media not all and (min-width:48rem){header #indicators{flex-shrink:0;order:2}}@media (min-width:48rem){header #indicators{gap:calc(var(--spacing)*8)}}header #indicators span[data-indicator]{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);cursor:pointer;font-size:0}header #indicators span[data-indicator]:before{content:var(--tw-content);content:var(--tw-content);width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);content:var(--tw-content);content:var(--tw-content);color:var(--foreground);background-color:currentColor;position:absolute}header #indicators span[data-indicator][data-indicator=media_error]:before{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M99.5149%20622.549C93.2144%20633.653%2093.4349%20634%20107.075%20634H651.912C665.537%20634%20665.757%20633.622%20659.473%20622.549L385.196%20138.236C378.99%20127.258%20379.998%20127.258%20373.792%20138.236L99.5149%20622.534V622.549ZM440.01%20107.161L714.303%20591.475C744.577%20644.978%20713.373%20697%20651.912%20697H107.075C45.5668%20697%2014.4423%20644.946%2044.7005%20591.506L318.977%20107.192C349.314%2053.5947%20409.705%2053.6105%20440.026%20107.192L440.01%20107.161ZM379.494%20586.75C383.631%20586.75%20387.728%20585.936%20391.551%20584.353C395.373%20582.77%20398.847%20580.45%20401.773%20577.525C404.698%20574.6%20407.019%20571.127%20408.603%20567.305C410.186%20563.483%20411.001%20559.387%20411.001%20555.25C411.001%20551.112%20410.186%20547.016%20408.603%20543.194C407.019%20539.372%20404.698%20535.899%20401.773%20532.974C398.847%20530.049%20395.373%20527.729%20391.551%20526.146C387.728%20524.563%20383.631%20523.749%20379.494%20523.749C371.14%20523.751%20363.128%20527.07%20357.221%20532.977C351.314%20538.885%20347.996%20546.896%20347.996%20555.25C347.996%20563.603%20351.314%20571.614%20357.221%20577.522C363.128%20583.429%20371.14%20586.748%20379.494%20586.75ZM347.991%20303.249V460.749C347.991%20469.104%20351.31%20477.116%20357.218%20483.023C363.126%20488.931%20371.139%20492.249%20379.494%20492.249C387.849%20492.249%20395.862%20488.931%20401.769%20483.023C407.677%20477.116%20410.996%20469.104%20410.996%20460.749V303.249C410.996%20294.894%20407.677%20286.882%20401.769%20280.975C395.862%20275.067%20387.849%20271.749%20379.494%20271.749C371.139%20271.749%20363.126%20275.067%20357.218%20280.975C351.31%20286.882%20347.991%20294.894%20347.991%20303.249Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M99.5149%20622.549C93.2144%20633.653%2093.4349%20634%20107.075%20634H651.912C665.537%20634%20665.757%20633.622%20659.473%20622.549L385.196%20138.236C378.99%20127.258%20379.998%20127.258%20373.792%20138.236L99.5149%20622.534V622.549ZM440.01%20107.161L714.303%20591.475C744.577%20644.978%20713.373%20697%20651.912%20697H107.075C45.5668%20697%2014.4423%20644.946%2044.7005%20591.506L318.977%20107.192C349.314%2053.5947%20409.705%2053.6105%20440.026%20107.192L440.01%20107.161ZM379.494%20586.75C383.631%20586.75%20387.728%20585.936%20391.551%20584.353C395.373%20582.77%20398.847%20580.45%20401.773%20577.525C404.698%20574.6%20407.019%20571.127%20408.603%20567.305C410.186%20563.483%20411.001%20559.387%20411.001%20555.25C411.001%20551.112%20410.186%20547.016%20408.603%20543.194C407.019%20539.372%20404.698%20535.899%20401.773%20532.974C398.847%20530.049%20395.373%20527.729%20391.551%20526.146C387.728%20524.563%20383.631%20523.749%20379.494%20523.749C371.14%20523.751%20363.128%20527.07%20357.221%20532.977C351.314%20538.885%20347.996%20546.896%20347.996%20555.25C347.996%20563.603%20351.314%20571.614%20357.221%20577.522C363.128%20583.429%20371.14%20586.748%20379.494%20586.75ZM347.991%20303.249V460.749C347.991%20469.104%20351.31%20477.116%20357.218%20483.023C363.126%20488.931%20371.139%20492.249%20379.494%20492.249C387.849%20492.249%20395.862%20488.931%20401.769%20483.023C407.677%20477.116%20410.996%20469.104%20410.996%20460.749V303.249C410.996%20294.894%20407.677%20286.882%20401.769%20280.975C395.862%20275.067%20387.849%20271.749%20379.494%20271.749C371.139%20271.749%20363.126%20275.067%20357.218%20280.975C351.31%20286.882%20347.991%20294.894%20347.991%20303.249Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat}header #indicators span[data-indicator][data-indicator=poll-status][data-style=active]:before{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M201.032%20123.921C202.67%20122.913%20204.182%20121.936%20205.537%20121.023C257.614%2085.7368%20319.094%2066.9154%20382%2067.0003C555.974%2067.0003%20697%20208.026%20697%20382C697.086%20440.93%20680.568%20498.691%20649.34%20548.666C648.405%20550.159%20647.097%20551.383%20645.545%20552.216C643.993%20553.05%20642.25%20553.465%20640.489%20553.42C638.728%20553.376%20637.009%20552.873%20635.501%20551.962C633.993%20551.051%20632.748%20549.763%20631.889%20548.225L552.509%20405.404C551.176%20403.007%20550.492%20400.303%20550.525%20397.56C550.558%20394.817%20551.307%20392.13%20552.698%20389.765C554.089%20387.401%20556.073%20385.44%20558.454%20384.078C560.836%20382.716%20563.531%20382%20566.275%20382H634C634.01%20335.412%20621.106%20289.734%20596.72%20250.038C572.334%20210.343%20537.422%20178.184%20495.861%20157.135C454.3%20136.085%20407.718%20126.968%20361.288%20130.797C314.858%20134.626%20270.398%20151.251%20232.847%20178.825C229.627%20181.188%20225.94%20182.839%20222.032%20183.668C218.124%20184.497%20214.085%20184.485%20210.182%20183.633C206.279%20182.781%20202.602%20181.108%20199.395%20178.726C196.189%20176.344%20193.525%20173.307%20191.582%20169.816L189.818%20166.666C185.825%20159.468%20184.763%20151.007%20186.852%20143.044C188.941%20135.082%20194.02%20128.232%20201.032%20123.921ZM558.715%20642.82C506.586%20678.213%20445.009%20697.092%20382%20697C208.025%20697%2067%20555.974%2067%20382C67%20322.969%2083.254%20267.718%20111.478%20220.5C112.696%20218.456%20114.432%20216.77%20116.509%20215.61C118.587%20214.45%20120.933%20213.858%20123.312%20213.893C125.691%20213.928%20128.019%20214.589%20130.061%20215.81C132.103%20217.03%20133.788%20218.768%20134.945%20220.846L211.49%20358.595C212.824%20360.993%20213.508%20363.697%20213.474%20366.44C213.441%20369.183%20212.692%20371.87%20211.302%20374.235C209.911%20376.599%20207.926%20378.56%20205.545%20379.922C203.164%20381.284%20200.468%20382%20197.725%20382H130C129.999%20428.603%20142.921%20474.294%20167.33%20513.993C191.739%20553.693%20226.679%20585.846%20268.265%20606.879C309.852%20627.913%20356.457%20637.002%20402.9%20633.137C449.342%20629.272%20493.803%20612.604%20531.341%20584.986C534.645%20582.52%20538.43%20580.775%20542.451%20579.865C546.472%20578.954%20550.64%20578.898%20554.684%20579.7C558.728%20580.501%20562.558%20582.143%20565.928%20584.519C569.297%20586.894%20572.131%20589.951%20574.244%20593.491C578.821%20601.138%20580.227%20610.272%20578.161%20618.941C576.095%20627.61%20570.721%20635.129%20563.188%20639.89C561.702%20640.833%20560.232%20641.799%20558.778%20642.788L558.715%20642.82Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M201.032%20123.921C202.67%20122.913%20204.182%20121.936%20205.537%20121.023C257.614%2085.7368%20319.094%2066.9154%20382%2067.0003C555.974%2067.0003%20697%20208.026%20697%20382C697.086%20440.93%20680.568%20498.691%20649.34%20548.666C648.405%20550.159%20647.097%20551.383%20645.545%20552.216C643.993%20553.05%20642.25%20553.465%20640.489%20553.42C638.728%20553.376%20637.009%20552.873%20635.501%20551.962C633.993%20551.051%20632.748%20549.763%20631.889%20548.225L552.509%20405.404C551.176%20403.007%20550.492%20400.303%20550.525%20397.56C550.558%20394.817%20551.307%20392.13%20552.698%20389.765C554.089%20387.401%20556.073%20385.44%20558.454%20384.078C560.836%20382.716%20563.531%20382%20566.275%20382H634C634.01%20335.412%20621.106%20289.734%20596.72%20250.038C572.334%20210.343%20537.422%20178.184%20495.861%20157.135C454.3%20136.085%20407.718%20126.968%20361.288%20130.797C314.858%20134.626%20270.398%20151.251%20232.847%20178.825C229.627%20181.188%20225.94%20182.839%20222.032%20183.668C218.124%20184.497%20214.085%20184.485%20210.182%20183.633C206.279%20182.781%20202.602%20181.108%20199.395%20178.726C196.189%20176.344%20193.525%20173.307%20191.582%20169.816L189.818%20166.666C185.825%20159.468%20184.763%20151.007%20186.852%20143.044C188.941%20135.082%20194.02%20128.232%20201.032%20123.921ZM558.715%20642.82C506.586%20678.213%20445.009%20697.092%20382%20697C208.025%20697%2067%20555.974%2067%20382C67%20322.969%2083.254%20267.718%20111.478%20220.5C112.696%20218.456%20114.432%20216.77%20116.509%20215.61C118.587%20214.45%20120.933%20213.858%20123.312%20213.893C125.691%20213.928%20128.019%20214.589%20130.061%20215.81C132.103%20217.03%20133.788%20218.768%20134.945%20220.846L211.49%20358.595C212.824%20360.993%20213.508%20363.697%20213.474%20366.44C213.441%20369.183%20212.692%20371.87%20211.302%20374.235C209.911%20376.599%20207.926%20378.56%20205.545%20379.922C203.164%20381.284%20200.468%20382%20197.725%20382H130C129.999%20428.603%20142.921%20474.294%20167.33%20513.993C191.739%20553.693%20226.679%20585.846%20268.265%20606.879C309.852%20627.913%20356.457%20637.002%20402.9%20633.137C449.342%20629.272%20493.803%20612.604%20531.341%20584.986C534.645%20582.52%20538.43%20580.775%20542.451%20579.865C546.472%20578.954%20550.64%20578.898%20554.684%20579.7C558.728%20580.501%20562.558%20582.143%20565.928%20584.519C569.297%20586.894%20572.131%20589.951%20574.244%20593.491C578.821%20601.138%20580.227%20610.272%20578.161%20618.941C576.095%20627.61%20570.721%20635.129%20563.188%20639.89C561.702%20640.833%20560.232%20641.799%20558.778%20642.788L558.715%20642.82Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat}header #indicators span[data-indicator][data-indicator=poll-status][data-style=inactive]:before{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M571.418%20595.528C580.7%20609.514%20576.962%20628.288%20563.018%20637.696C533.911%20657.268%20510.181%20670.456%20491.868%20677.302C456.716%20690.382%20419.502%20697.054%20381.995%20697C354.694%20697%20328.234%20693.514%20302.949%20687.004L335.962%20629.8C396.485%20641.014%20459.864%20629.8%20513.289%20597.124L530.089%20586.708C536.769%20582.478%20544.84%20581.032%20552.573%20582.681C560.306%20584.329%20567.086%20588.941%20571.46%20595.528H571.418ZM509.803%2094.006C529.123%20102.574%20547.477%20113.074%20564.53%20125.212L250.869%20668.482C231.733%20659.714%20213.526%20649.048%20196.52%20636.646L509.761%2094.006H509.803ZM381.995%2067C409.295%2067%20435.839%2070.486%20461.082%2076.996L427.985%20134.284L422.063%20133.234C371.12%20125.021%20318.883%20132.645%20272.412%20155.075C225.941%20177.505%20187.476%20213.659%20162.214%20258.653C136.953%20303.647%20126.112%20355.311%20131.158%20406.664C136.204%20458.017%20156.893%20506.583%20190.43%20545.8L157.459%20602.92C113.918%20558.666%2084.4251%20502.532%2072.6843%20441.57C60.9436%20380.609%2067.4788%20317.537%2091.469%20260.277C115.459%20203.018%20155.835%20154.123%20207.524%20119.736C259.213%2085.349%20319.912%2067.0021%20381.995%2067ZM606.572%20161.08C664.623%20219.944%20697.117%20299.327%20697%20382C697.058%20427.158%20687.374%20471.797%20668.607%20512.872C664.536%20521.456%20660.05%20529.838%20655.167%20537.988C653.67%20540.539%20651.526%20542.649%20648.952%20544.106C646.378%20545.563%20643.465%20546.315%20640.508%20546.285C637.55%20546.255%20634.653%20545.445%20632.109%20543.936C629.564%20542.428%20627.464%20540.274%20626.019%20537.694L609.932%20508.714L553.357%20406.948C551.939%20404.39%20551.212%20401.505%20551.251%20398.581C551.289%20395.656%20552.09%20392.791%20553.575%20390.271C555.06%20387.751%20557.177%20385.662%20559.717%20384.212C562.258%20382.761%20565.132%20381.998%20568.058%20382H633.999C634.02%20321.959%20612.568%20263.891%20573.518%20218.284L606.572%20161.122V161.08Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M571.418%20595.528C580.7%20609.514%20576.962%20628.288%20563.018%20637.696C533.911%20657.268%20510.181%20670.456%20491.868%20677.302C456.716%20690.382%20419.502%20697.054%20381.995%20697C354.694%20697%20328.234%20693.514%20302.949%20687.004L335.962%20629.8C396.485%20641.014%20459.864%20629.8%20513.289%20597.124L530.089%20586.708C536.769%20582.478%20544.84%20581.032%20552.573%20582.681C560.306%20584.329%20567.086%20588.941%20571.46%20595.528H571.418ZM509.803%2094.006C529.123%20102.574%20547.477%20113.074%20564.53%20125.212L250.869%20668.482C231.733%20659.714%20213.526%20649.048%20196.52%20636.646L509.761%2094.006H509.803ZM381.995%2067C409.295%2067%20435.839%2070.486%20461.082%2076.996L427.985%20134.284L422.063%20133.234C371.12%20125.021%20318.883%20132.645%20272.412%20155.075C225.941%20177.505%20187.476%20213.659%20162.214%20258.653C136.953%20303.647%20126.112%20355.311%20131.158%20406.664C136.204%20458.017%20156.893%20506.583%20190.43%20545.8L157.459%20602.92C113.918%20558.666%2084.4251%20502.532%2072.6843%20441.57C60.9436%20380.609%2067.4788%20317.537%2091.469%20260.277C115.459%20203.018%20155.835%20154.123%20207.524%20119.736C259.213%2085.349%20319.912%2067.0021%20381.995%2067ZM606.572%20161.08C664.623%20219.944%20697.117%20299.327%20697%20382C697.058%20427.158%20687.374%20471.797%20668.607%20512.872C664.536%20521.456%20660.05%20529.838%20655.167%20537.988C653.67%20540.539%20651.526%20542.649%20648.952%20544.106C646.378%20545.563%20643.465%20546.315%20640.508%20546.285C637.55%20546.255%20634.653%20545.445%20632.109%20543.936C629.564%20542.428%20627.464%20540.274%20626.019%20537.694L609.932%20508.714L553.357%20406.948C551.939%20404.39%20551.212%20401.505%20551.251%20398.581C551.289%20395.656%20552.09%20392.791%20553.575%20390.271C555.06%20387.751%20557.177%20385.662%20559.717%20384.212C562.258%20382.761%20565.132%20381.998%20568.058%20382H633.999C634.02%20321.959%20612.568%20263.891%20573.518%20218.284L606.572%20161.122V161.08Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat}header #indicators span[data-indicator][data-indicator=uci-changes]{position:relative}header #indicators span[data-indicator][data-indicator=uci-changes]:before{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1758476543825'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='17352'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M439.594667%2085.333333h144.810666a70.4%2070.4%200%200%201%2070.101334%2060.970667l5.802666%2044.8a17.066667%2017.066667%200%200%200%208.192%2012.458667l38.058667%2022.528a17.066667%2017.066667%200%200%200%2015.146667%201.109333l46.848-19.2a71.082667%2071.082667%200%200%201%2088.021333%2029.226667l72.405333%20122.069333c18.218667%2030.72%2010.069333%2069.973333-18.688%2091.221333l-45.184%2033.408a17.066667%2017.066667%200%200%200-6.912%2013.696v28.757334a17.066667%2017.066667%200%200%200%206.912%2013.696l45.226667%2033.408c28.714667%2021.205333%2036.864%2060.458667%2018.645333%2091.221333l-72.405333%20122.026667a71.082667%2071.082667%200%200%201-88.021333%2029.226666l-46.848-19.2a17.066667%2017.066667%200%200%200-15.146667%201.152l-38.058667%2022.528a17.066667%2017.066667%200%200%200-8.192%2012.501334l-5.802666%2044.8A70.4%2070.4%200%200%201%20584.405333%20938.666667h-144.810666a70.4%2070.4%200%200%201-70.101334-60.970667l-5.802666-44.8a17.066667%2017.066667%200%200%200-8.192-12.458667l-38.058667-22.528a17.066667%2017.066667%200%200%200-15.146667-1.109333l-46.848%2019.2a71.082667%2071.082667%200%200%201-88.021333-29.226667l-72.405333-122.069333a69.290667%2069.290667%200%200%201%2018.688-91.221333l45.184-33.408a17.066667%2017.066667%200%200%200%206.912-13.696v-28.757334a17.066667%2017.066667%200%200%200-6.912-13.696l-45.226667-33.408a69.290667%2069.290667%200%200%201-18.645333-91.221333l72.405333-122.026667a71.082667%2071.082667%200%200%201%2088.021333-29.226666l46.848%2019.2a17.066667%2017.066667%200%200%200%2015.146667-1.152l38.058667-22.528a17.066667%2017.066667%200%200%200%208.192-12.501334l5.802666-44.8A70.4%2070.4%200%200%201%20439.594667%2085.333333z%20m1.024%20130.688a75.818667%2075.818667%200%200%201-36.821334%2055.466667l-51.712%2030.464a76.970667%2076.970667%200%200%201-67.925333%204.906667l-35.328-14.336a17.066667%2017.066667%200%200%200-21.077333%207.04L178.645333%20381.738667a17.066667%2017.066667%200%200%200%204.565334%2022.528l33.066666%2024.277333c19.541333%2014.293333%2031.018667%2036.906667%2031.018667%2061.013333v44.885334c0%2024.106667-11.52%2046.72-31.018667%2061.013333l-33.066666%2024.277333a17.066667%2017.066667%200%200%200-4.565334%2022.528l49.066667%2082.176a17.066667%2017.066667%200%200%200%2021.12%207.04l35.328-14.336a76.970667%2076.970667%200%200%201%2067.925333%204.906667l51.712%2030.421333c20.224%2011.946667%2033.792%2032.384%2036.821334%2055.509334l4.010666%2030.506666a17.066667%2017.066667%200%200%200%2016.896%2014.848h100.949334a17.066667%2017.066667%200%200%200%2016.896-14.848l4.010666-30.506666c2.986667-23.125333%2016.597333-43.605333%2036.821334-55.466667l51.712-30.464a76.970667%2076.970667%200%200%201%2067.925333-4.906667l35.328%2014.336a17.066667%2017.066667%200%200%200%2021.077333-7.04l49.109334-82.176a17.066667%2017.066667%200%200%200-4.565334-22.528l-33.066666-24.277333a75.648%2075.648%200%200%201-31.018667-61.013333v-44.885334c0-24.106667%2011.52-46.72%2031.018667-61.013333l33.066666-24.277333a17.066667%2017.066667%200%200%200%204.565334-22.528l-49.066667-82.176a17.066667%2017.066667%200%200%200-21.12-7.04l-35.328%2014.336c-22.186667%209.045333-47.317333%207.210667-67.925333-4.906667l-51.712-30.421333a75.818667%2075.818667%200%200%201-36.821334-55.509334l-4.010666-30.506666A17.066667%2017.066667%200%200%200%20562.474667%20170.666667h-100.949334a17.066667%2017.066667%200%200%200-16.896%2014.848l-4.010666%2030.506666zM682.666667%20512a42.666667%2042.666667%200%200%201-85.333334%200%2085.333333%2085.333333%200%201%200-85.333333%2085.333333%2042.666667%2042.666667%200%200%201%200%2085.333334%20170.666667%20170.666667%200%201%201%20170.666667-170.666667z'%20fill='%230f162b'%20p-id='17353'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1758476543825'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='17352'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M439.594667%2085.333333h144.810666a70.4%2070.4%200%200%201%2070.101334%2060.970667l5.802666%2044.8a17.066667%2017.066667%200%200%200%208.192%2012.458667l38.058667%2022.528a17.066667%2017.066667%200%200%200%2015.146667%201.109333l46.848-19.2a71.082667%2071.082667%200%200%201%2088.021333%2029.226667l72.405333%20122.069333c18.218667%2030.72%2010.069333%2069.973333-18.688%2091.221333l-45.184%2033.408a17.066667%2017.066667%200%200%200-6.912%2013.696v28.757334a17.066667%2017.066667%200%200%200%206.912%2013.696l45.226667%2033.408c28.714667%2021.205333%2036.864%2060.458667%2018.645333%2091.221333l-72.405333%20122.026667a71.082667%2071.082667%200%200%201-88.021333%2029.226666l-46.848-19.2a17.066667%2017.066667%200%200%200-15.146667%201.152l-38.058667%2022.528a17.066667%2017.066667%200%200%200-8.192%2012.501334l-5.802666%2044.8A70.4%2070.4%200%200%201%20584.405333%20938.666667h-144.810666a70.4%2070.4%200%200%201-70.101334-60.970667l-5.802666-44.8a17.066667%2017.066667%200%200%200-8.192-12.458667l-38.058667-22.528a17.066667%2017.066667%200%200%200-15.146667-1.109333l-46.848%2019.2a71.082667%2071.082667%200%200%201-88.021333-29.226667l-72.405333-122.069333a69.290667%2069.290667%200%200%201%2018.688-91.221333l45.184-33.408a17.066667%2017.066667%200%200%200%206.912-13.696v-28.757334a17.066667%2017.066667%200%200%200-6.912-13.696l-45.226667-33.408a69.290667%2069.290667%200%200%201-18.645333-91.221333l72.405333-122.026667a71.082667%2071.082667%200%200%201%2088.021333-29.226666l46.848%2019.2a17.066667%2017.066667%200%200%200%2015.146667-1.152l38.058667-22.528a17.066667%2017.066667%200%200%200%208.192-12.501334l5.802666-44.8A70.4%2070.4%200%200%201%20439.594667%2085.333333z%20m1.024%20130.688a75.818667%2075.818667%200%200%201-36.821334%2055.466667l-51.712%2030.464a76.970667%2076.970667%200%200%201-67.925333%204.906667l-35.328-14.336a17.066667%2017.066667%200%200%200-21.077333%207.04L178.645333%20381.738667a17.066667%2017.066667%200%200%200%204.565334%2022.528l33.066666%2024.277333c19.541333%2014.293333%2031.018667%2036.906667%2031.018667%2061.013333v44.885334c0%2024.106667-11.52%2046.72-31.018667%2061.013333l-33.066666%2024.277333a17.066667%2017.066667%200%200%200-4.565334%2022.528l49.066667%2082.176a17.066667%2017.066667%200%200%200%2021.12%207.04l35.328-14.336a76.970667%2076.970667%200%200%201%2067.925333%204.906667l51.712%2030.421333c20.224%2011.946667%2033.792%2032.384%2036.821334%2055.509334l4.010666%2030.506666a17.066667%2017.066667%200%200%200%2016.896%2014.848h100.949334a17.066667%2017.066667%200%200%200%2016.896-14.848l4.010666-30.506666c2.986667-23.125333%2016.597333-43.605333%2036.821334-55.466667l51.712-30.464a76.970667%2076.970667%200%200%201%2067.925333-4.906667l35.328%2014.336a17.066667%2017.066667%200%200%200%2021.077333-7.04l49.109334-82.176a17.066667%2017.066667%200%200%200-4.565334-22.528l-33.066666-24.277333a75.648%2075.648%200%200%201-31.018667-61.013333v-44.885334c0-24.106667%2011.52-46.72%2031.018667-61.013333l33.066666-24.277333a17.066667%2017.066667%200%200%200%204.565334-22.528l-49.066667-82.176a17.066667%2017.066667%200%200%200-21.12-7.04l-35.328%2014.336c-22.186667%209.045333-47.317333%207.210667-67.925333-4.906667l-51.712-30.421333a75.818667%2075.818667%200%200%201-36.821334-55.509334l-4.010666-30.506666A17.066667%2017.066667%200%200%200%20562.474667%20170.666667h-100.949334a17.066667%2017.066667%200%200%200-16.896%2014.848l-4.010666%2030.506666zM682.666667%20512a42.666667%2042.666667%200%200%201-85.333334%200%2085.333333%2085.333333%200%201%200-85.333333%2085.333333%2042.666667%2042.666667%200%200%201%200%2085.333334%20170.666667%20170.666667%200%201%201%20170.666667-170.666667z'%20fill='%230f162b'%20p-id='17353'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat}header #indicators span[data-indicator][data-indicator=uci-changes][data-count]:not([data-count="0"]):after{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*-.5);content:var(--tw-content);right:calc(var(--spacing)*-.5);content:var(--tw-content);z-index:10;content:var(--tw-content);content:var(--tw-content);min-height:calc(var(--spacing)*3);content:var(--tw-content);min-width:calc(var(--spacing)*3);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);background-color:var(--accent);content:var(--tw-content);padding-inline:calc(var(--spacing)*.5);content:var(--tw-content);content:var(--tw-content);--tw-leading:1;content:var(--tw-content);--tw-font-weight:var(--font-weight-bold);font-size:8px;line-height:1;font-weight:var(--font-weight-bold);content:var(--tw-content);color:var(--accent-foreground);content:var(--tw-content);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-content:attr(data-count);content:var(--tw-content);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex;position:absolute}#maincontent{width:95.8333%;min-height:calc(100vh - 4rem);max-width:var(--container-max-width);padding-inline:calc(var(--spacing)*4);margin-inline:auto}@media not all and (min-width:48rem){#maincontent{width:100%;padding-inline:calc(var(--spacing)*3)}}#maincontent #view{margin-inline:calc(var(--spacing)*0);width:100%;animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none);padding:calc(var(--spacing)*0);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-enter-opacity:0;--tw-animation-fill-mode:both;--tw-enter-translate-y:calc(2*var(--spacing)*-1);transition-duration:.3s;animation-fill-mode:both}#maincontent #view:empty{display:none}@media (min-width:48rem){#maincontent #view{border-radius:calc(var(--radius-base)*4);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#maincontent #view{border-color:color-mix(in oklab,var(--border)30%,transparent)}}#maincontent #view{background-color:var(--page-bg);padding:calc(var(--spacing)*6);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){#maincontent #view:hover{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#maincontent #view:hover{border-color:color-mix(in oklab,var(--border)50%,transparent)}}#maincontent #view:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}}#maincontent #view .cbi-title-section{margin-bottom:calc(var(--spacing)*6);--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}@media not all and (min-width:48rem){#maincontent #view .cbi-title-section{margin-inline:calc(var(--spacing)*2);margin-bottom:calc(var(--spacing)*3)}}#maincontent #view .controls{margin:calc(var(--spacing)*2);align-items:center;gap:calc(var(--spacing)*3);flex-wrap:wrap;display:flex}@media not all and (min-width:48rem){#maincontent #view .controls{margin:calc(var(--spacing)*1);gap:calc(var(--spacing)*2)}}#maincontent #view .controls label input[type=radio],#maincontent #view .controls label input[type=checkbox]{margin-right:calc(var(--spacing)*1);vertical-align:middle}#maincontent #view #content_syslog{margin:calc(var(--spacing)*2)}@media not all and (min-width:48rem){#maincontent #view #content_syslog{margin:calc(var(--spacing)*1)}}#maincontent #view #content_syslog>div{align-items:baseline;column-gap:calc(var(--spacing)*2);flex-wrap:wrap;display:flex}@media not all and (min-width:48rem){#maincontent #view #content_syslog>div{column-gap:calc(var(--spacing)*1.5)}#maincontent #view #content_syslog>div>.cbi-input-select,#maincontent #view #content_syslog>div>.cbi-input-text{min-width:calc(var(--spacing)*45)}}#maincontent #view div[style]>svg:where([data-darkmode=true],[data-darkmode=true] *){background-color:var(--page-bg)!important}#maincontent #view div[style]>svg line[style]:where([data-darkmode=true],[data-darkmode=true] *){stroke:var(--foreground)!important}#maincontent .cbi-map-descr,#maincontent .cbi-section-descr{margin-bottom:calc(var(--spacing)*6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--muted-foreground)}@media not all and (min-width:48rem){#maincontent .cbi-map-descr,#maincontent .cbi-section-descr{margin-inline:calc(var(--spacing)*2);margin-bottom:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}#maincontent .cbi-page-actions{margin-top:calc(var(--spacing)*6);justify-content:flex-end;align-items:center;gap:calc(var(--spacing)*3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border);flex-wrap:wrap;display:flex}@supports (color:color-mix(in lab, red, red)){#maincontent .cbi-page-actions{border-color:color-mix(in oklab,var(--border)50%,transparent)}}#maincontent .cbi-page-actions{padding-top:calc(var(--spacing)*4)}@media not all and (min-width:48rem){#maincontent .cbi-page-actions{margin-inline:calc(var(--spacing)*2);margin-top:calc(var(--spacing)*4);gap:calc(var(--spacing)*2);padding-top:calc(var(--spacing)*3)}}#maincontent .zone-forwards{align-items:flex-start;gap:calc(var(--spacing)*3);--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);display:flex}#maincontent .zone-forwards>span{margin-top:calc(var(--spacing)*1.5);color:var(--muted-foreground)}#maincontent .zone-forwards .zone-dest{align-items:flex-start;gap:calc(var(--spacing)*2);flex-direction:column;display:flex}#maincontent #syslog,#maincontent #log_textarea{width:100%;font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}#maincontent #syslog{border-radius:calc(var(--radius-base)*3);border-style:var(--tw-border-style);background-color:var(--terminal-bg);padding:calc(var(--spacing)*6);color:var(--terminal-foreground);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-width:1px}@media not all and (min-width:48rem){#maincontent #syslog{padding:calc(var(--spacing)*3)}}footer{margin-top:calc(var(--spacing)*2);min-height:calc(var(--spacing)*16);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--secondary-foreground);flex-wrap:wrap;justify-content:space-between;align-items:center;display:flex}footer a{color:var(--primary)}footer span{text-align:center;display:inline-block}footer .breadcrumb{align-items:center;gap:calc(var(--spacing)*1);background-color:var(--page-bg);border-radius:3.40282e38px;display:flex}@supports (color:color-mix(in lab, red, red)){footer .breadcrumb{background-color:color-mix(in oklab,var(--page-bg)60%,transparent)}}footer .breadcrumb{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}@media (hover:hover){footer .breadcrumb:hover{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){footer .breadcrumb:hover{background-color:color-mix(in oklab,var(--page-bg)80%,transparent)}}footer .breadcrumb:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}footer .breadcrumb li{list-style-type:none}footer .breadcrumb li.active a{color:var(--default-foreground)}.control-group{margin-top:calc(var(--spacing)*1)}.control-group,.cbi-page-actions>div,.cbi-section-actions>div{align-items:center;gap:calc(var(--spacing)*2);flex-flow:row;display:flex}@media not all and (min-width:48rem){.control-group,.cbi-page-actions>div,.cbi-section-actions>div{gap:calc(var(--spacing)*1);flex-wrap:wrap}}.theme-switcher{align-items:center;gap:calc(var(--spacing)*0);background-color:var(--page-bg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;border-radius:3.40282e38px;transition-duration:.3s;display:inline-flex;position:relative}@media not all and (min-width:48rem){.theme-switcher{padding-block:calc(var(--spacing)*.5)}}.theme-switcher{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.theme-switcher{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.theme-switcher:before{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*1);content:var(--tw-content);left:calc(var(--spacing)*1);content:var(--tw-content);z-index:0;content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);background-color:var(--border);border-radius:3.40282e38px;width:calc(33.333% - .5rem);height:calc(100% - .5rem);position:absolute}@supports (color:color-mix(in lab, red, red)){.theme-switcher:before{background-color:color-mix(in oklab,var(--border)50%,transparent)}}.theme-switcher:before{content:var(--tw-content);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);content:var(--tw-content);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);content:var(--tw-content);--tw-ring-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.theme-switcher:before{--tw-ring-color:color-mix(in oklab,var(--border)60%,transparent)}}.theme-switcher:before{content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));content:var(--tw-content);--tw-duration:.3s;transition-duration:.3s}@media not all and (min-width:48rem){footer .theme-switcher{display:none}}.theme-switcher .theme-option{z-index:10;cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;border-radius:3.40282e38px;transition-duration:.3s;display:flex;position:relative}@media (hover:hover){.theme-switcher .theme-option:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.theme-switcher .theme-option:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.theme-switcher .theme-option input[type=radio]{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.theme-switcher .theme-option .theme-icon{color:var(--muted-foreground);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;justify-content:center;align-items:center;transition-duration:.3s;display:flex}.theme-switcher .theme-option .theme-icon svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}.theme-switcher .theme-option .theme-label{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--muted-foreground);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s;display:none}.theme-switcher .theme-option.active .theme-icon{color:var(--foreground)}.theme-switcher .theme-option.active .theme-icon svg{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.theme-switcher .theme-option.active .theme-label,.theme-switcher .theme-option:hover:not(.active) .theme-icon,.theme-switcher .theme-option:hover:not(.active) .theme-label{color:var(--foreground)}.theme-switcher:has(.theme-option[data-theme=device].active):before{content:var(--tw-content);--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.theme-switcher:has(.theme-option[data-theme=light].active):before{content:var(--tw-content);--tw-translate-x:calc(100% + .5rem);translate:var(--tw-translate-x)var(--tw-translate-y)}.theme-switcher:has(.theme-option[data-theme=dark].active):before{content:var(--tw-content);--tw-translate-x:calc(200% + 1rem);translate:var(--tw-translate-x)var(--tw-translate-y)}.floating-toolbar{right:calc(var(--spacing)*4);bottom:calc(var(--spacing)*4);z-index:40;flex-direction:column;align-items:center;display:flex;position:fixed}@media not all and (min-width:48rem){.floating-toolbar{right:calc(var(--spacing)*3);bottom:calc(var(--spacing)*3)}}.floating-toolbar .toolbar-list{visibility:visible;margin-bottom:calc(var(--spacing)*2);transform-origin:bottom;opacity:1;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.4s;--tw-ease:var(--ease-in-out);transition-duration:.4s;transition-timing-function:var(--ease-in-out);grid-template-rows:1fr;display:grid}@media not all and (min-width:48rem){.floating-toolbar .toolbar-list{margin-bottom:calc(var(--spacing)*1.5)}}.floating-toolbar .toolbar-list .toolbar-list-inner{min-height:calc(var(--spacing)*0);align-items:center;gap:calc(var(--spacing)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);border-radius:3.40282e38px;flex-direction:column;display:flex;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.floating-toolbar .toolbar-list .toolbar-list-inner{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.floating-toolbar .toolbar-list .toolbar-list-inner{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){.floating-toolbar .toolbar-list .toolbar-list-inner{background-color:color-mix(in oklab,var(--page-bg)90%,transparent)}}.floating-toolbar .toolbar-list .toolbar-list-inner{padding:calc(var(--spacing)*1);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media not all and (min-width:48rem){.floating-toolbar .toolbar-list .toolbar-list-inner{gap:calc(var(--spacing)*1.5);padding:calc(var(--spacing)*.5)}}.floating-toolbar .toolbar-btn{height:calc(var(--spacing)*10);width:calc(var(--spacing)*10);cursor:pointer;padding:calc(var(--spacing)*2);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-in-out);transition-duration:.3s;transition-timing-function:var(--ease-in-out);border-radius:3.40282e38px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.floating-toolbar .toolbar-btn:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}@media not all and (min-width:48rem){.floating-toolbar .toolbar-btn{height:calc(var(--spacing)*9);width:calc(var(--spacing)*9);padding:calc(var(--spacing)*1.5)}}.floating-toolbar .toolbar-btn .icon{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-in-out);transition-duration:.3s;transition-timing-function:var(--ease-in-out);flex-shrink:0}@media not all and (min-width:48rem){.floating-toolbar .toolbar-btn .icon{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4)}}.floating-toolbar .toolbar-btn:not(.toggle){text-decoration-line:none}@media (hover:hover){.floating-toolbar .toolbar-btn:not(.toggle):hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y);background-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.floating-toolbar .toolbar-btn:not(.toggle):hover{background-color:color-mix(in oklab,var(--border)40%,transparent)}}}.floating-toolbar .toolbar-btn:not(.toggle) .icon{object-fit:contain}.floating-toolbar .toolbar-btn:not(.toggle) .icon:where([data-darkmode=true],[data-darkmode=true] *){--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.floating-toolbar .toolbar-btn.toggle{border-style:var(--tw-border-style);background-color:var(--page-bg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-width:0}.floating-toolbar .toolbar-btn.toggle .icon{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.4s;--tw-ease:var(--ease-out);transition-duration:.4s;transition-timing-function:var(--ease-out)}.floating-toolbar.collapsed .toolbar-list{pointer-events:none;visibility:hidden;margin-bottom:calc(var(--spacing)*0);--tw-translate-y:calc(var(--spacing)*2);translate:var(--tw-translate-x)var(--tw-translate-y);--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x)var(--tw-scale-y);opacity:0;grid-template-rows:0fr}.floating-toolbar.collapsed .toolbar-btn:not(.toggle){--tw-scale-x:50%;--tw-scale-y:50%;--tw-scale-z:50%;scale:var(--tw-scale-x)var(--tw-scale-y);opacity:0}.floating-toolbar.collapsed .toolbar-btn.toggle .icon{rotate:45deg}button,.btn,.cbi-button{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing)*1.5);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-width:1px;display:inline-flex}@media not all and (min-width:48rem){button,.btn,.cbi-button{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}}:is(button,.btn,.cbi-button)[disabled]{cursor:not-allowed;opacity:.4}:is(button,.btn,.cbi-button)[disabled]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}.td :is(button,.btn,.cbi-button){white-space:nowrap;flex:none;width:auto;display:inline-flex}@media not all and (min-width:48rem){.td :is(button,.btn,.cbi-button){min-height:calc(var(--spacing)*9);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}.btn,.cbi-button,.cbi-button-default,.cbi-button-download,.cbi-button-find,.cbi-button-link,.cbi-button-up,.cbi-button-down{border-color:var(--secondary);background-color:var(--secondary);color:var(--secondary-foreground)}@media (hover:hover){:is(.btn,.cbi-button,.cbi-button-default,.cbi-button-download,.cbi-button-find,.cbi-button-link,.cbi-button-up,.cbi-button-down):hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){:is(.btn,.cbi-button,.cbi-button-default,.cbi-button-download,.cbi-button-find,.cbi-button-link,.cbi-button-up,.cbi-button-down):hover{background-color:color-mix(in oklab,var(--secondary)90%,transparent)}}}.drag-handle{border-color:var(--muted);background-color:var(--muted);color:var(--muted-foreground)}@media (hover:hover){.drag-handle:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.drag-handle:hover{background-color:color-mix(in oklab,var(--muted)90%,transparent)}}}.btn.primary,.cbi-button-action,.cbi-button-apply,.cbi-button-reload,.cbi-button-add,.cbi-button-edit{border-color:var(--primary);background-color:var(--primary);color:var(--primary-foreground)}@media (hover:hover){:is(.btn.primary,.cbi-button-action,.cbi-button-apply,.cbi-button-reload,.cbi-button-add,.cbi-button-edit):hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){:is(.btn.primary,.cbi-button-action,.cbi-button-apply,.cbi-button-reload,.cbi-button-add,.cbi-button-edit):hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}}}.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{border-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{border-color:color-mix(in oklab,var(--primary)25%,transparent)}}.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{background-color:color-mix(in oklab,var(--primary)25%,transparent)}}.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{color:var(--primary)}@media (hover:hover){:is(.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save):hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){:is(.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save):hover{background-color:color-mix(in oklab,var(--primary)30%,transparent)}}}.cbi-button-negative,.cbi-section-remove .cbi-button,.cbi-button-reset,.cbi-button-remove{border-color:var(--destructive);background-color:var(--destructive);color:var(--destructive-foreground)}@media (hover:hover){:is(.cbi-button-negative,.cbi-section-remove .cbi-button,.cbi-button-reset,.cbi-button-remove):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){:is(.cbi-button-negative,.cbi-section-remove .cbi-button,.cbi-button-reset,.cbi-button-remove):hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}}}input:disabled{cursor:not-allowed;opacity:.4}input:disabled:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}input[type=text],input[type=password],.cbi-input-text,.cbi-input{border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);position:relative}@supports (color:color-mix(in lab, red, red)){input[type=text],input[type=password],.cbi-input-text,.cbi-input{border-color:color-mix(in oklab,var(--border)70%,transparent)}}input[type=text],input[type=password],.cbi-input-text,.cbi-input{background-color:var(--page-bg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--foreground)}:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input)::placeholder{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input)::placeholder{color:color-mix(in oklab,var(--muted-foreground)70%,transparent)}}input[type=text],input[type=password],.cbi-input-text,.cbi-input{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input):focus{border-color:var(--primary);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input):focus{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input):focus{--tw-outline-style:none;outline-style:none}.table.cbi-section-table :is(input[type=text],input[type=password],.cbi-input-text,.cbi-input){width:100%}.cbi-input-text{min-width:calc(var(--spacing)*20)}@media not all and (min-width:48rem){.cbi-input-text{min-width:calc(var(--spacing)*14)}}#localtime{min-width:calc(var(--spacing)*70)}input[type=radio],input[type=checkbox]{margin-right:calc(var(--spacing)*3);height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);cursor:pointer;appearance:none;display:inline-block;position:relative}:is(input[type=radio],input[type=checkbox]):before{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*0);content:var(--tw-content);left:calc(var(--spacing)*0);content:var(--tw-content);height:calc(var(--spacing)*4);content:var(--tw-content);width:calc(var(--spacing)*4);content:var(--tw-content);border-style:var(--tw-border-style);content:var(--tw-content);border-width:1px;border-color:var(--border);position:absolute}@supports (color:color-mix(in lab, red, red)){:is(input[type=radio],input[type=checkbox]):before{border-color:color-mix(in oklab,var(--border)70%,transparent)}}:is(input[type=radio],input[type=checkbox]):before{content:var(--tw-content);background-color:var(--page-bg)}:is(input[type=radio],input[type=checkbox]):after{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*.5);content:var(--tw-content);left:calc(var(--spacing)*.5);content:var(--tw-content);height:calc(var(--spacing)*3);content:var(--tw-content);width:calc(var(--spacing)*3);content:var(--tw-content);background-color:var(--input-checked);content:var(--tw-content);opacity:0;content:var(--tw-content);transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));content:var(--tw-content);--tw-duration:.2s;transition-duration:.2s;position:absolute}:is(input[type=radio],input[type=checkbox]):checked:before{content:var(--tw-content);border-color:var(--primary);content:var(--tw-content);background-color:var(--primary)}:is(input[type=radio],input[type=checkbox]):checked:after{content:var(--tw-content);opacity:1}@media (hover:hover){:is(input[type=radio],input[type=checkbox]):hover:before{content:var(--tw-content);border-color:var(--border)}}:is(input[type=radio],input[type=checkbox]):focus:before{content:var(--tw-content);border-color:var(--primary);content:var(--tw-content);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);content:var(--tw-content);--tw-ring-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){:is(input[type=radio],input[type=checkbox]):focus:before{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}:is(input[type=radio],input[type=checkbox]):focus:before{content:var(--tw-content);--tw-outline-style:none;outline-style:none}:is(input[type=radio],input[type=checkbox]):disabled{cursor:not-allowed}input[type=radio]:before{content:var(--tw-content);border-radius:3.40282e38px}input[type=radio]:after{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='currentColor'%3e%3ccircle%20cx='12'%20cy='12'%20r='6'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='currentColor'%3e%3ccircle%20cx='12'%20cy='12'%20r='6'/%3e%3c/svg%3e") 50%/cover no-repeat}input[type=checkbox]:before{content:var(--tw-content);border-radius:calc(var(--radius-base)*.5)}input[type=checkbox]:after{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1760890864667'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='18898'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M918.8%2083.7c-43.5-30.3-104-19.5-134.6%2024.1l-425.8%20606-127-135.7c-36.4-38.9-98.1-41.1-137-4.8-39%2036.2-41.1%2097.7-4.7%20136.6l206.5%20220.7c4.7%205%209.8%209.4%2015.1%2013.1%200.7%200.5%201.4%201%202.2%201.5%2043.5%2030.3%20104%2019.5%20134.6-24.1l494-703.2c30.7-43.5%2020.1-103.9-23.3-134.2z'%20fill='%230f162b'%20p-id='18899'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1760890864667'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='18898'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M918.8%2083.7c-43.5-30.3-104-19.5-134.6%2024.1l-425.8%20606-127-135.7c-36.4-38.9-98.1-41.1-137-4.8-39%2036.2-41.1%2097.7-4.7%20136.6l206.5%20220.7c4.7%205%209.8%209.4%2015.1%2013.1%200.7%200.5%201.4%201%202.2%201.5%2043.5%2030.3%20104%2019.5%20134.6-24.1l494-703.2c30.7-43.5%2020.1-103.9-23.3-134.2z'%20fill='%230f162b'%20p-id='18899'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat}.cbi-checkbox{align-items:center;display:inline-flex;position:relative}textarea{min-height:calc(var(--spacing)*24);resize:vertical;border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);width:100%}@supports (color:color-mix(in lab, red, red)){textarea{border-color:color-mix(in oklab,var(--border)70%,transparent)}}textarea{background-color:var(--page-bg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--foreground)}textarea::placeholder{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){textarea::placeholder{color:color-mix(in oklab,var(--muted-foreground)70%,transparent)}}textarea{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}textarea:focus{border-color:var(--primary);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){textarea:focus{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}textarea:focus{--tw-outline-style:none;outline-style:none}textarea[disabled]{cursor:not-allowed;opacity:.4}textarea[disabled]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}select{appearance:none;border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){select{border-color:color-mix(in oklab,var(--border)70%,transparent)}}select{background-color:var(--page-bg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);padding-right:calc(var(--spacing)*10);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--foreground);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}select:focus{border-color:var(--primary);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){select:focus{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}select:focus{--tw-outline-style:none;outline-style:none}select{background-image:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M5.22%208.22a.75.75%200%200%201%201.06%200L10%2011.94l3.72-3.72a.75.75%200%201%201%201.06%201.06l-4.25%204.25a.75.75%200%200%201-1.06%200L5.22%209.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e");background-position:right .75rem center;background-repeat:no-repeat;background-size:16px}select:where([data-darkmode=true],[data-darkmode=true] *){background-image:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23ffffff'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M5.22%208.22a.75.75%200%200%201%201.06%200L10%2011.94l3.72-3.72a.75.75%200%201%201%201.06%201.06l-4.25%204.25a.75.75%200%200%201-1.06%200L5.22%209.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e")}select[disabled]{cursor:not-allowed;opacity:.4}select[disabled]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}.cbi-dropdown{min-width:fit-content;max-width:100%;height:fit-content;padding:calc(var(--spacing)*0);white-space:nowrap;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);flex-shrink:0;align-items:center;display:inline-flex}.cbi-dropdown[disabled]{pointer-events:none;opacity:.4}.cbi-dropdown[disabled]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}.cbi-dropdown:not(.btn):not(.cbi-button){border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown:not(.btn):not(.cbi-button){border-color:color-mix(in oklab,var(--border)70%,transparent)}}.cbi-dropdown:not(.btn):not(.cbi-button){background-color:var(--page-bg)}.cbi-dropdown:not(.btn):not(.cbi-button)>ul>li[placeholder]{display:none}.cbi-dropdown>ul{margin:calc(var(--spacing)*0);align-items:center;gap:calc(var(--spacing)*.5);flex-wrap:nowrap;flex:1;list-style-type:none;display:flex;overflow:hidden}@media not all and (min-width:48rem){.cbi-dropdown>ul{flex-wrap:wrap}}.cbi-dropdown>ul>li{margin:calc(var(--spacing)*0);align-items:center;gap:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1.5);list-style-type:none;display:none;overflow:hidden}.cbi-dropdown>ul>li[display]{display:flex!important}.cbi-dropdown>ul>li .hide-open{display:block}.cbi-dropdown>ul>li .hide-close{display:none}.cbi-dropdown>ul>li>form{pointer-events:none;display:none}.cbi-dropdown>ul>li>label{align-items:center;gap:calc(var(--spacing)*2);display:flex}.cbi-dropdown>ul>li img{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5);vertical-align:middle;flex:none}.cbi-dropdown>ul>li span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.cbi-dropdown>ul.dropdown{left:calc(var(--spacing)*0);z-index:60;border-radius:var(--radius-base);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);width:fit-content;min-width:100%;position:absolute;overflow-y:auto}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown>ul.dropdown{border-color:color-mix(in oklab,var(--border)60%,transparent)}}.cbi-dropdown>ul.dropdown{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown>ul.dropdown{background-color:color-mix(in oklab,var(--page-bg)95%,transparent)}}.cbi-dropdown>ul.dropdown{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.cbi-dropdown>ul.dropdown>li{min-height:calc(var(--spacing)*9);cursor:pointer;width:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--muted-foreground)}@media (hover:hover){.cbi-dropdown>ul.dropdown>li:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown>ul.dropdown>li:hover{background-color:color-mix(in oklab,var(--primary)15%,transparent)}}}.cbi-dropdown>ul.preview{display:none}.cbi-dropdown[empty]>ul{max-width:1px;max-height:1px}.cbi-dropdown[empty]>ul>li,.cbi-dropdown[optional][open]>ul.dropdown>li[placeholder]{display:block}.cbi-dropdown[open]{border-color:var(--primary);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary);position:relative}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown[open]{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}.cbi-dropdown[open]>ul.dropdown{width:auto;max-width:none;display:block}.cbi-dropdown[open]>ul.dropdown>li{display:flex}.cbi-dropdown[open]>ul.dropdown>li[selected]{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown[open]>ul.dropdown>li[selected]{background-color:color-mix(in oklab,var(--primary)20%,transparent)}}.cbi-dropdown[open]>ul.dropdown>li[selected]{color:var(--primary)}.cbi-dropdown[open]>ul.dropdown>li[unselectable]{opacity:.4}.cbi-dropdown[open]>ul.dropdown>li[unselectable]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}.cbi-dropdown[open]>ul.dropdown>li .hide-open{display:none}.cbi-dropdown[open]>ul.dropdown>li .hide-close{display:block}.cbi-dropdown[open]>ul.preview{display:flex}:is(.cbi-dropdown[multiple][more],.cbi-dropdown[multiple][empty])>.more{padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*1.5);flex:none;display:flex}.cbi-dropdown[multiple][open]>ul.dropdown>li>form{align-items:center;display:flex}.cbi-dropdown[multiple]>ul>li>label{display:flex}.cbi-dropdown>.open{cursor:pointer;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--border);flex:none;justify-content:center;align-items:center;display:flex}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown>.open{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.cbi-dropdown>.open{padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:currentColor}.cbi-dropdown>.more{display:none}.cbi-tooltip{z-index:110;max-width:var(--container-xs);--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);border-radius:calc(var(--radius-base)*1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);position:absolute}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip{border-color:color-mix(in oklab,var(--border)60%,transparent)}}.cbi-tooltip{background-color:var(--tooltip-bg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));overflow-wrap:normal;word-break:normal;white-space:normal;color:var(--foreground);opacity:0;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-out);transition-duration:.3s;transition-timing-function:var(--ease-out)}.cbi-tooltip.error{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip.error{border-color:color-mix(in oklab,var(--error)40%,transparent)}}.cbi-tooltip.error{background-color:var(--error);color:var(--error-foreground)}.cbi-tooltip.success{border-color:var(--success)}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip.success{border-color:color-mix(in oklab,var(--success)40%,transparent)}}.cbi-tooltip.success{background-color:var(--success);color:var(--success-foreground)}.cbi-tooltip.info{border-color:var(--info)}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip.info{border-color:color-mix(in oklab,var(--info)40%,transparent)}}.cbi-tooltip.info{background-color:var(--info);color:var(--info-foreground)}.cbi-tooltip.notice{border-color:var(--warning)}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip.notice{border-color:color-mix(in oklab,var(--warning)40%,transparent)}}.cbi-tooltip.notice{background-color:var(--warning);color:var(--warning-foreground)}.cbi-tooltip-container,[data-tooltip]{cursor:help;white-space:nowrap;text-underline-offset:2px;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;text-decoration-line:underline;text-decoration-style:dotted;text-decoration-color:currentColor;transition-duration:.2s;display:inline-block;position:relative}:is(.cbi-tooltip-container,[data-tooltip]) .cbi-tooltip{visibility:hidden;--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);opacity:0;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-out);transition-duration:.3s;transition-timing-function:var(--ease-out)}:is(.cbi-tooltip-container,[data-tooltip]):hover .cbi-tooltip{visibility:visible;--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y);opacity:1}.alert-message{top:calc(var(--spacing)*14);z-index:40;margin-bottom:calc(var(--spacing)*4);border-radius:calc(var(--radius-base)*4);border-style:var(--tw-border-style);border-width:1px;border-color:var(--default);position:sticky}@supports (color:color-mix(in lab, red, red)){.alert-message{border-color:color-mix(in oklab,var(--default)30%,transparent)}}.alert-message{background-color:var(--default);padding:calc(var(--spacing)*6);color:var(--default-foreground)}@media not all and (min-width:48rem){.alert-message{margin-inline:calc(var(--spacing)*0);margin-bottom:calc(var(--spacing)*3);border-radius:calc(var(--radius-base)*3);padding:calc(var(--spacing)*4)}}.alert-message.modal{position:static;top:auto}.modal .alert-message{margin-bottom:calc(var(--spacing)*0)}.login-shell .alert-message{margin-bottom:calc(var(--spacing)*0);border-radius:calc(var(--radius-base)*2);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2.5);text-align:center;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}@media not all and (min-width:48rem){.login-shell .alert-message{padding-inline:calc(var(--spacing)*.5)}}.alert-message.success{border-color:var(--success)}@supports (color:color-mix(in lab, red, red)){.alert-message.success{border-color:color-mix(in oklab,var(--success)30%,transparent)}}.alert-message.success{background-color:var(--success);color:var(--success-foreground)}.alert-message.info{border-color:var(--info)}@supports (color:color-mix(in lab, red, red)){.alert-message.info{border-color:color-mix(in oklab,var(--info)30%,transparent)}}.alert-message.info{background-color:var(--info);color:var(--info-foreground)}.alert-message.warning,.alert-message.notice{border-color:var(--warning)}@supports (color:color-mix(in lab, red, red)){.alert-message.warning,.alert-message.notice{border-color:color-mix(in oklab,var(--warning)30%,transparent)}}.alert-message.warning,.alert-message.notice{background-color:var(--warning);color:var(--warning-foreground)}.alert-message.error,.alert-message.danger{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.alert-message.error,.alert-message.danger{border-color:color-mix(in oklab,var(--error)30%,transparent)}}.alert-message.error,.alert-message.danger{background-color:var(--error);color:var(--error-foreground)}.alert-message h4,.alert-message h5,.alert-message pre,.alert-message ul,.alert-message li,.alert-message p{border-style:var(--tw-border-style);color:inherit;background-color:#0000;border-width:0}.alert-message h4{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}@media not all and (min-width:48rem){.alert-message h4{margin-bottom:calc(var(--spacing)*1.5);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.alert-message p{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}@media not all and (min-width:48rem){.alert-message p{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}.alert-message .right{margin-top:calc(var(--spacing)*4)}@media not all and (min-width:48rem){.alert-message .right{margin-top:calc(var(--spacing)*3)}}.cbi-progressbar{height:calc(var(--spacing)*3.5);cursor:help;background-color:var(--secondary);border-radius:3.40282e38px;width:100%;position:relative;overflow:hidden}.cbi-progressbar:before{content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);content:var(--tw-content);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);content:var(--tw-content);border-radius:calc(var(--radius-base)*2);content:var(--tw-content);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));content:var(--tw-content);white-space:nowrap;content:var(--tw-content);color:var(--foreground);--tw-content:attr(title);content:var(--tw-content);position:absolute;top:50%;left:50%}@media not all and (min-width:48rem){.cbi-progressbar{height:calc(var(--spacing)*4);border-radius:calc(var(--radius-base)*2)}.cbi-progressbar:before{content:var(--tw-content);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));content:var(--tw-content);--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}}@media not all and (min-width:40rem){[data-page=admin-system-package-manager] .cbi-progressbar:before{content:var(--tw-content);font-size:10px}}.cbi-progressbar>div{--tw-gradient-position:to right;height:100%}@supports (background-image:linear-gradient(in lab, red, red)){.cbi-progressbar>div{--tw-gradient-position:to right in oklab}}.cbi-progressbar>div{background-image:linear-gradient(var(--tw-gradient-stops));--tw-gradient-from:var(--progress-bar-start);--tw-gradient-to:var(--progress-bar-end);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}.label{background-color:var(--default);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--default-foreground);text-transform:uppercase;border-radius:3.40282e38px}.label.important{background-color:var(--info);color:var(--info-foreground)}.label.warning{background-color:var(--error);color:var(--error-foreground)}.label.success{background-color:var(--success);color:var(--success-foreground)}.label.notice{background-color:var(--warning);color:var(--warning-foreground)}.zonebadge{align-items:center;gap:calc(var(--spacing)*1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--secondary);background-color:var(--secondary);padding-inline:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--secondary-foreground);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-radius:3.40282e38px;display:inline-flex;overflow:visible}@media (hover:hover){.zonebadge:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){.zonebadge{gap:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.zonebadge[style*=--zone-color-rgb]{background-color:rgb(var(--zone-color-rgb),.7)!important}.zonebadge[style*=--zone-color-rgb] em{color:var(--default-foreground)}.cbi-dropdown .zonebadge{border-radius:calc(var(--radius-base)*1.5);padding-inline:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*.5)}.ifacebadge{cursor:default;align-items:center;gap:calc(var(--spacing)*2);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--label-surface);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--default-foreground);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);flex-wrap:wrap;display:inline-flex}@media (hover:hover){.ifacebadge:hover{border-color:var(--border);background-color:var(--label-surface);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){.ifacebadge{gap:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.cbi-dropdown .ifacebadge,.cbi-tooltip .ifacebadge{flex-wrap:nowrap}.zonebadge>.ifacebadge,.cbi-dropdown .ifacebadge{border-radius:calc(var(--radius-base)*2);padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*0)}:is(.zonebadge>.ifacebadge,.cbi-dropdown .ifacebadge) img{margin-right:calc(var(--spacing)*1);width:calc(var(--spacing)*4)}.ifacebadge>img{width:calc(var(--spacing)*5);flex-shrink:0;align-self:center}@media not all and (min-width:48rem){.ifacebadge>img{width:calc(var(--spacing)*4)}}.ifacebadge>span>.nowrap{margin-bottom:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));display:inline-block}@media not all and (min-width:48rem){.ifacebadge>span>.nowrap{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.ifacebadge.ifacebadge-active{border-color:var(--primary);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.ifacebadge.large{min-width:200px;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);flex:1}@media not all and (min-width:48rem){.ifacebadge.large{min-width:180px;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2.5)}}.cbi-section{margin-bottom:calc(var(--spacing)*3);border-radius:calc(var(--radius-base)*2);padding:calc(var(--spacing)*5);overflow:visible}@media not all and (min-width:48rem){.cbi-section{padding:calc(var(--spacing)*3)}}.cbi-section[data-tab-title]{margin:calc(var(--spacing)*0);padding:calc(var(--spacing)*0)}.cbi-section[data-tab-active=true]{margin-bottom:calc(var(--spacing)*3);padding:calc(var(--spacing)*5)}@media not all and (min-width:48rem){.cbi-section[data-tab-active=true]{padding:calc(var(--spacing)*3)}}#maincontent .cbi-section{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#maincontent .cbi-section{border-color:color-mix(in oklab,var(--border)30%,transparent)}}#maincontent .cbi-section{background-color:var(--panel-bg);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){#maincontent .cbi-section:hover{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#maincontent .cbi-section:hover{border-color:color-mix(in oklab,var(--border)50%,transparent)}}#maincontent .cbi-section:hover{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.cbi-section>.cbi-title{justify-content:space-between;align-items:flex-start;display:flex}.cbi-section>.cbi-title>h3{flex:1}.cbi-section>.cbi-title>h3 .label[data-clickable]{background-color:var(--label-surface);color:var(--foreground);width:calc(var(--spacing)*7);height:calc(var(--spacing)*7);cursor:pointer;padding:calc(var(--spacing)*0);justify-content:center;align-items:center;display:inline-flex}.cbi-section>.cbi-title>h3 .label[data-clickable]:before{content:var(--tw-content);width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);content:var(--tw-content);content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));content:var(--tw-content);--tw-duration:.3s;background-color:currentColor;transition-duration:.3s}@media (hover:hover){.cbi-section>.cbi-title>h3 .label[data-clickable]:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.cbi-section>.cbi-title>h3 .label[data-clickable]:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}@media not all and (min-width:48rem){.cbi-section>.cbi-title>h3 .label[data-clickable]{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status]{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);cursor:pointer;font-size:0;position:relative}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status]:before{content:var(--tw-content);content:var(--tw-content);width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);content:var(--tw-content);content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));content:var(--tw-content);--tw-duration:.3s;content:var(--tw-content);--tw-ease:var(--ease-out);transition-duration:.3s;transition-timing-function:var(--ease-out);background-color:currentColor;position:absolute}@media (hover:hover){.cbi-section>.cbi-title>h3 span[data-indicator=poll-status]:hover:before{content:var(--tw-content);--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status]:active:before{content:var(--tw-content);--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status][data-style=active]:before{content:var(--tw-content);content:var(--tw-content);rotate:none;-webkit-mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M8.22%205.22a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L11.94%2010L8.22%206.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M8.22%205.22a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L11.94%2010L8.22%206.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status][data-style=inactive]:before{content:var(--tw-content);content:var(--tw-content);rotate:90deg;-webkit-mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M8.22%205.22a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L11.94%2010L8.22%206.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M8.22%205.22a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L11.94%2010L8.22%206.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat}.cbi-section legend{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.cbi-section h3{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.cbi-section h3{border-color:color-mix(in oklab,var(--border)50%,transparent)}}.cbi-section h3{padding-bottom:calc(var(--spacing)*4);color:var(--foreground)}@media not all and (min-width:48rem){.cbi-section h3{margin-inline:calc(var(--spacing)*0);padding-bottom:calc(var(--spacing)*2)}.cbi-section div[style*=display\:grid]{gap:calc(var(--spacing)*3);grid-template-columns:repeat(2,minmax(0,1fr))!important}}.cbi-section .network-status-table{margin-bottom:calc(var(--spacing)*3);justify-content:space-around;gap:calc(var(--spacing)*4);flex-wrap:wrap;display:flex}@media not all and (min-width:48rem){.cbi-section .network-status-table{flex-direction:column}}.cbi-section .cbi-section-create{margin-top:calc(var(--spacing)*6);align-items:center;gap:calc(var(--spacing)*3);flex-wrap:wrap;display:flex}@media not all and (min-width:48rem){.cbi-section .cbi-section-create{margin-inline:calc(var(--spacing)*0);margin-top:calc(var(--spacing)*5);align-items:stretch;gap:calc(var(--spacing)*3);flex-direction:column}}.cbi-section .cbi-section-create>div{flex:none}@media not all and (min-width:48rem){.cbi-section .cbi-section-create>div{flex:1;width:100%}}.ifacebox{min-width:calc(var(--spacing)*40);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--panel-bg);flex-direction:column;align-items:stretch;display:inline-flex;position:relative;overflow:visible}@supports (color:color-mix(in lab, red, red)){.ifacebox{background-color:color-mix(in oklab,var(--panel-bg)95%,transparent)}}.ifacebox{text-align:center;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){.ifacebox:hover{border-color:var(--border);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){.ifacebox{min-width:calc(var(--spacing)*30);border-radius:calc(var(--radius-base)*3);border-color:var(--border);flex:1}@supports (color:color-mix(in lab, red, red)){.ifacebox{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.ifacebox{background-color:var(--panel-bg)}@supports (color:color-mix(in lab, red, red)){.ifacebox{background-color:color-mix(in oklab,var(--panel-bg)98%,transparent)}}.ifacebox{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}td .ifacebox{flex-direction:row}}.ifacebox .ifacebox-head{border-top-left-radius:calc(var(--radius-base)*2);border-top-right-radius:calc(var(--radius-base)*2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);background-color:var(--label-surface);width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);text-align:center;--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--foreground);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}@media not all and (min-width:48rem){.ifacebox .ifacebox-head{border-top-left-radius:calc(var(--radius-base)*3);border-top-right-radius:calc(var(--radius-base)*3);border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.ifacebox .ifacebox-head{border-color:color-mix(in oklab,var(--border)80%,transparent)}}.ifacebox .ifacebox-head{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}}.ifacebox .ifacebox-head[style*=--zone-color-rgb]{background-color:rgb(var(--zone-color-rgb),.75)!important}@media not all and (min-width:48rem){td :is(.ifacebox .ifacebox-head){border-top-left-radius:calc(var(--radius-base)*3);border-bottom-left-radius:calc(var(--radius-base)*3);border-right-style:var(--tw-border-style);border-right-width:1px;border-bottom-style:var(--tw-border-style);border-bottom-width:0;border-top-right-radius:0;flex-shrink:0;justify-content:center;align-items:center;width:auto;display:flex}}.ifacebox .ifacebox-head.cbi-tooltip-container{background-color:#0000;position:static}.ifacebox .ifacebox-head.cbi-tooltip-container .cbi-tooltip{bottom:calc(var(--spacing)*0);left:calc(var(--spacing)*0)}.ifacebox .ifacebox-head.active{border-color:var(--primary);background-color:var(--primary);color:var(--primary-foreground)}.ifacebox .ifacebox-body{justify-content:space-around;align-items:center;gap:calc(var(--spacing)*2);border-bottom-right-radius:calc(var(--radius-base)*2);border-bottom-left-radius:calc(var(--radius-base)*2);width:100%;padding:calc(var(--spacing)*4);text-align:center;color:var(--default-foreground);flex-direction:column;flex:1;display:flex}@media not all and (min-width:48rem){.ifacebox .ifacebox-body{border-bottom-right-radius:calc(var(--radius-base)*3);border-bottom-left-radius:calc(var(--radius-base)*3)}td :is(.ifacebox .ifacebox-body){border-top-right-radius:calc(var(--radius-base)*3);border-bottom-right-radius:calc(var(--radius-base)*3);padding-block:calc(var(--spacing)*2);padding-right:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*4);border-bottom-left-radius:0;flex-direction:row}}:where(.ifacebox .ifacebox-body>span>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1.5)*calc(1 - var(--tw-space-y-reverse)))}.ifacebox .ifacebox-body>span{color:var(--foreground)}@media not all and (min-width:48rem){:where(.ifacebox .ifacebox-body>span>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}.ifacebox .ifacebox-body>span{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}}.network-status-table :is(.ifacebox .ifacebox-body>span){width:100%}.ifacebox .ifacebox-body>span .cbi-tooltip-container+.cbi-tooltip-container{margin-left:calc(var(--spacing)*2)}@media not all and (min-width:48rem){.ifacebox .ifacebox-body>span .cbi-tooltip-container+.cbi-tooltip-container{margin-left:calc(var(--spacing)*1.5)}}.ifacebox .ifacebox-body>span .cbi-tooltip{--tw-animation-delay:.2s;transition-delay:.2s;animation-delay:.2s}.ifacebox .ifacebox-body>span .nowrap{display:inline-block}.ifacebox .ifacebox-body>span>.nowrap:not(:last-child){margin-bottom:calc(var(--spacing)*4)}@media not all and (min-width:48rem){.ifacebox .ifacebox-body>span>.nowrap:not(:last-child){margin-bottom:calc(var(--spacing)*3)}}.ifacebox .ifacebox-body>span img{width:calc(var(--spacing)*6);flex-shrink:0}@media not all and (min-width:48rem){.ifacebox .ifacebox-body>span img{width:calc(var(--spacing)*5)}}.ifacebox .ifacebox-body>div{flex-wrap:wrap;width:100%;display:flex}:where(.ifacebox .ifacebox-body>div>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2.5)*calc(1 - var(--tw-space-y-reverse)))}@media not all and (min-width:48rem){:where(.ifacebox .ifacebox-body>div>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1.5)*calc(1 - var(--tw-space-y-reverse)))}td :is(.ifacebox .ifacebox-body>div){flex:1;width:auto}:where(td :is(.ifacebox .ifacebox-body>div)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}}.ifacebox .ifacebox-body>div .ifacebadge{flex:1}.ifacebox .ifacebox-body .cbi-tooltip-container{cursor:help;color:var(--foreground);justify-content:center;align-items:center;display:inline-flex;position:static}.ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip{bottom:calc(var(--spacing)*0)}@media (min-width:48rem){.ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip{left:calc(var(--spacing)*0)}}.ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip .nowrap{white-space:nowrap}.ifacebox .ifacebox-body small{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:calc(var(--spacing)*4);line-height:calc(var(--spacing)*4);display:block}@media not all and (min-width:48rem){td :is(.ifacebox .ifacebox-body small){margin-top:calc(var(--spacing)*0)}}#modal_overlay{pointer-events:none;top:calc(var(--spacing)*0);bottom:calc(var(--spacing)*0);background-color:#0000;display:none;position:fixed;overflow:auto}.modal-overlay-active #modal_overlay{pointer-events:auto;inset:calc(var(--spacing)*0);right:calc(var(--spacing)*0);left:calc(var(--spacing)*0);z-index:100;background-color:var(--overlay-base);grid-template-columns:repeat(1,minmax(0,1fr));place-items:center;display:grid}@supports (color:color-mix(in lab, red, red)){.modal-overlay-active #modal_overlay{background-color:color-mix(in oklab,var(--overlay-base)70%,transparent)}}.modal-overlay-active #modal_overlay{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}#modal_overlay>.modal{width:var(--container-5xl);gap:calc(var(--spacing)*4);border-radius:calc(var(--radius-base)*3);border-style:var(--tw-border-style);border-width:1px;border-color:color-mix(in oklab,var(--border)70%,transparent);background-color:var(--page-bg);padding:calc(var(--spacing)*6);overflow-wrap:break-word;white-space:normal;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-lg));--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);flex-direction:column;display:flex}@media not all and (min-width:48rem){#modal_overlay>.modal{gap:calc(var(--spacing)*3);width:100%;padding:calc(var(--spacing)*4)}}#modal_overlay>.modal h4{margin-bottom:calc(var(--spacing)*0);text-align:center}#modal_overlay>.modal h4 em{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--primary)}#modal_overlay>.modal h5{margin-block:calc(var(--spacing)*3);color:var(--foreground)}#modal_overlay>.modal p{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--default-foreground)}#modal_overlay>.modal>ul{border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);overflow:auto}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal>ul{border-color:color-mix(in oklab,var(--border)60%,transparent)}}#modal_overlay>.modal>ul{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal>ul{background-color:color-mix(in oklab,var(--page-bg)60%,transparent)}}#modal_overlay>.modal>ul{padding:calc(var(--spacing)*3)}#modal_overlay>.modal label.btn{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal label.btn{border-color:color-mix(in oklab,var(--border)60%,transparent)}}#modal_overlay>.modal label.btn{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal label.btn{background-color:color-mix(in oklab,var(--page-bg)60%,transparent)}}#modal_overlay>.modal label.btn{color:var(--foreground)}@media (hover:hover){#modal_overlay>.modal label.btn:hover{border-color:var(--border)}}#modal_overlay>.modal pre{overflow:auto}#modal_overlay>.modal pre.errors{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal pre.errors{border-color:color-mix(in oklab,var(--error)60%,transparent)}}#modal_overlay>.modal pre.errors{background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal pre.errors{background-color:color-mix(in oklab,var(--error)20%,transparent)}}#modal_overlay>.modal pre.errors{color:var(--error-foreground)}#modal_overlay>.modal pre+pre{margin-top:calc(var(--spacing)*3)}#modal_overlay>.modal .button-row,#modal_overlay>.modal div.left,#modal_overlay>.modal div.right{gap:calc(var(--spacing)*3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border);flex-shrink:0;display:flex}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal .button-row,#modal_overlay>.modal div.left,#modal_overlay>.modal div.right{border-color:color-mix(in oklab,var(--border)40%,transparent)}}#modal_overlay>.modal .button-row,#modal_overlay>.modal div.left,#modal_overlay>.modal div.right{padding:calc(var(--spacing)*4)}@media not all and (min-width:48rem){#modal_overlay>.modal .button-row,#modal_overlay>.modal div.left,#modal_overlay>.modal div.right{gap:calc(var(--spacing)*1.5);padding:calc(var(--spacing)*2.5)}}#modal_overlay>.modal div.left{justify-content:flex-start}#modal_overlay>.modal .button-row,#modal_overlay>.modal div.right{justify-content:flex-end}#modal_overlay>.modal.uci-dialog ins,#modal_overlay>.modal.uci-dialog del,#modal_overlay>.modal.uci-dialog var{font-family:var(--font-mono);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);font-style:normal;text-decoration-line:none}#modal_overlay>.modal.uci-dialog ins{border-style:var(--tw-border-style);border-width:1px;border-color:var(--success);background-color:var(--success)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog ins{background-color:color-mix(in oklab,var(--success)80%,transparent)}}#modal_overlay>.modal.uci-dialog ins{color:var(--success-foreground)}#modal_overlay>.modal.uci-dialog del{border-style:var(--tw-border-style);border-width:1px;border-color:var(--error);background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog del{background-color:color-mix(in oklab,var(--error)80%,transparent)}}#modal_overlay>.modal.uci-dialog del{color:var(--error-foreground)}#modal_overlay>.modal.uci-dialog var{border-style:var(--tw-border-style);border-width:1px;border-color:var(--info);background-color:var(--info)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog var{background-color:color-mix(in oklab,var(--info)80%,transparent)}}#modal_overlay>.modal.uci-dialog var{color:var(--info-foreground)}#modal_overlay>.modal.uci-dialog .uci-change-legend{margin-top:calc(var(--spacing)*4);gap:calc(var(--spacing)*3);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog .uci-change-legend{border-color:color-mix(in oklab,var(--border)60%,transparent)}}#modal_overlay>.modal.uci-dialog .uci-change-legend{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog .uci-change-legend{background-color:color-mix(in oklab,var(--page-bg)60%,transparent)}}#modal_overlay>.modal.uci-dialog .uci-change-legend{padding:calc(var(--spacing)*4)}@media not all and (min-width:48rem){#modal_overlay>.modal.uci-dialog .uci-change-legend{gap:calc(var(--spacing)*2);padding:calc(var(--spacing)*3);grid-template-columns:repeat(1,minmax(0,1fr))}}#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label{align-items:center;gap:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--foreground);display:flex}#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label ins,#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label del,#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label var{height:calc(var(--spacing)*6);min-width:calc(var(--spacing)*6);border-radius:var(--radius-base);padding-inline:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);justify-content:center;align-items:center;display:flex}#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label var ins,#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label var del{margin-left:calc(var(--spacing)*1);height:calc(var(--spacing)*4);min-width:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*1)}#modal_overlay>.modal.uci-dialog .uci-change-list{margin-top:calc(var(--spacing)*4)}:where(#modal_overlay>.modal.uci-dialog .uci-change-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}#modal_overlay>.modal.uci-dialog .uci-change-list h5{margin-top:calc(var(--spacing)*6);margin-bottom:calc(var(--spacing)*2);border-radius:calc(var(--radius-base)*1.5);background-color:var(--default);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--default-foreground)}#modal_overlay>.modal.uci-dialog .uci-change-list h5:first-child{margin-top:calc(var(--spacing)*0)}#modal_overlay>.modal.uci-dialog .uci-change-list ins,#modal_overlay>.modal.uci-dialog .uci-change-list del{border-radius:calc(var(--radius-base)*1.5);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));display:block}:is(#modal_overlay>.modal.uci-dialog .uci-change-list ins,#modal_overlay>.modal.uci-dialog .uci-change-list del) strong{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}#modal_overlay>.modal.uci-dialog .uci-change-list var{border-radius:var(--radius-base);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));display:inline-block}#modal_overlay>.modal.uci-dialog .uci-change-list var ins,#modal_overlay>.modal.uci-dialog .uci-change-list var del{margin-left:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));display:inline-block}@media not all and (min-width:48rem){.mobile-menu-overlay{visibility:hidden;inset:calc(var(--spacing)*0);z-index:60;background-color:var(--overlay-base);position:fixed}@supports (color:color-mix(in lab, red, red)){.mobile-menu-overlay{background-color:color-mix(in oklab,var(--overlay-base)60%,transparent)}}.mobile-menu-overlay{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}}@media (min-width:48rem){.mobile-menu-overlay{display:none}}@media not all and (min-width:48rem){.mobile-menu-overlay.mobile-menu-open{visibility:visible;opacity:1;--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.mobile-menu-overlay.mobile-menu-open .mobile-nav{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.mobile-menu-overlay .mobile-nav{top:calc(var(--spacing)*0);right:calc(var(--spacing)*0);height:100%;width:calc(var(--spacing)*80);--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y);background-color:var(--page-bg);--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);--tw-backdrop-saturate:saturate(150%);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;flex-direction:column;transition-duration:.3s;display:flex;position:absolute}.mobile-menu-overlay .mobile-nav .mobile-nav-header{padding:calc(var(--spacing)*6);padding-bottom:calc(var(--spacing)*4);justify-content:space-between;align-items:center;display:flex}.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-title{margin:calc(var(--spacing)*0);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--foreground)}.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close{cursor:pointer;transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}@media (hover:hover){.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close svg{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}@media not all and (min-width:48rem){.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close svg{color:var(--foreground);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}.mobile-menu-overlay .mobile-nav .mobile-nav-list{margin:calc(var(--spacing)*0);gap:calc(var(--spacing)*0);padding:calc(var(--spacing)*0);padding-inline:calc(var(--spacing)*6);flex-direction:column;flex:1;list-style-type:none;display:flex;overflow-y:auto}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-link{width:100%;padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*4);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--default-foreground);transition-property:color,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;align-items:center;text-decoration-line:none;transition-duration:.3s;display:flex}@media (hover:hover){.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-link:hover{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y);color:var(--foreground)}}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu{margin:calc(var(--spacing)*0);padding:calc(var(--spacing)*0);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-in-out);transition-duration:.3s;transition-timing-function:var(--ease-in-out);list-style-type:none;overflow:hidden}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem{border-color:color-mix(in oklab,var(--border)20%,transparent)}}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem .mobile-nav-sublink{width:100%;padding-block:calc(var(--spacing)*3);padding-right:calc(var(--spacing)*0);padding-left:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--default-foreground);transition-property:color,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;align-items:center;text-decoration-line:none;transition-duration:.3s;display:flex}@media (hover:hover){.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem .mobile-nav-sublink:hover{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y);color:var(--foreground)}}}[data-nav-type=mega-menu] .desktop-menu-overlay{pointer-events:none;inset:calc(var(--spacing)*0);z-index:50;background-color:var(--overlay-base);position:fixed}@supports (color:color-mix(in lab, red, red)){[data-nav-type=mega-menu] .desktop-menu-overlay{background-color:color-mix(in oklab,var(--overlay-base)35%,transparent)}}[data-nav-type=mega-menu] .desktop-menu-overlay{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}@media not all and (min-width:48rem){[data-nav-type=mega-menu] .desktop-menu-overlay{display:none}}[data-nav-type=mega-menu] .desktop-menu-overlay.active{pointer-events:auto;opacity:1;--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}table.table,.table{margin-bottom:calc(var(--spacing)*3);border-collapse:separate;--tw-border-spacing-x:calc(var(--spacing)*0);--tw-border-spacing-y:calc(var(--spacing)*0);width:100%;border-spacing:var(--tw-border-spacing-x)var(--tw-border-spacing-y);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);overflow:visible}@media not all and (min-width:48rem){table.table,.table{margin-inline:calc(var(--spacing)*0);border-style:var(--tw-border-style);background-color:var(--panel-bg);border-width:0;width:100%;display:block;overflow:visible}@supports (color:color-mix(in lab, red, red)){table.table,.table{background-color:color-mix(in oklab,var(--panel-bg)98%,transparent)}}table.table,.table{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}:is(table.table,.table)[width="100%"]{width:100%}:is(table.table,.table)[width="33%"]{width:33.3333%}:is(table.table,.table) .cbi-section-table-titles{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):before{content:var(--tw-content);content:var(--tw-content);border-color:var(--border);content:var(--tw-content);padding-inline:calc(var(--spacing)*3);content:var(--tw-content);padding-block:calc(var(--spacing)*3);content:var(--tw-content);vertical-align:middle;content:var(--tw-content);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));content:var(--tw-content);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-content:attr(data-title);content:var(--tw-content);content:var(--tw-content);display:table-cell}@media not all and (min-width:48rem){:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):before{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));flex:0 0 100%;display:flex}}:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):before{content:var(--tw-content)}@media (min-width:48rem){:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):before{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}}:is(:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):not([data-title]),:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):has(td.cbi-section-table-titles)):before{content:var(--tw-content);--tw-content:none;content:none}:is(table.table,.table) .cbi-section-table-titles.named:before{content:var(--tw-content);background-color:var(--label-surface);content:var(--tw-content)}@media not all and (min-width:48rem){:is(table.table,.table) .cbi-section-table-titles.named:before{background-color:var(--label-surface)}}:is(table.table,.table) .cbi-section-table-titles.named:before{content:var(--tw-content)}@media (min-width:48rem){:is(table.table,.table) .cbi-section-table-titles.named:before{border-top-left-radius:calc(var(--radius-base)*2)}}@media not all and (min-width:48rem){:is(table.table,.table) tbody,:is(table.table,.table) .tbody{display:block}}@media (hover:hover){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):hover{background-color:var(--label-surface)}}@media not all and (min-width:48rem){:is(table.table,.table) tr,:is(table.table,.table) .tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);flex-wrap:wrap;display:flex}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):has(.cbi-progressbar){justify-content:center;align-items:center;display:flex}}@media (min-width:48rem){:is(:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child:not([data-title]) .th,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child:not([data-title]) .td):first-child{border-top-left-radius:calc(var(--radius-base)*2)}:is(:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child .th,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child .td):last-child{border-top-right-radius:calc(var(--radius-base)*2)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child{border-bottom-right-radius:calc(var(--radius-base)*2);border-bottom-left-radius:calc(var(--radius-base)*2)}}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child[data-title]:before{content:var(--tw-content);border-bottom-left-radius:calc(var(--radius-base)*2);content:var(--tw-content);border-bottom-style:var(--tw-border-style);border-bottom-width:0}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child .td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child .td:first-child{border-bottom-left-radius:calc(var(--radius-base)*2)}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child .td:last-child{border-bottom-right-radius:calc(var(--radius-base)*2)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).table-titles,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-section-table-titles,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-section-table-descr{display:none}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child:not(.table-titles,.cbi-section-table-titles,.cbi-section-table-descr),:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).table-titles+.tr,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-section-table-titles+.tr,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-section-table-descr+.tr{border-top-left-radius:calc(var(--radius-base)*2);border-top-right-radius:calc(var(--radius-base)*2)}}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-rowstyle-1{background-color:var(--panel-bg)}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-rowstyle-2{background-color:var(--label-surface)}@supports (color:color-mix(in lab, red, red)){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-rowstyle-2{background-color:color-mix(in oklab,var(--label-surface)50%,transparent)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-1{flex:2rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-1{width:calc(var(--spacing)*20);max-width:calc(var(--spacing)*28)}}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-2{white-space:normal}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-2{flex:2 2 4rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-2{width:calc(var(--spacing)*32);max-width:calc(var(--spacing)*40)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-3{flex:3 3 6rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-3{width:calc(var(--spacing)*44);max-width:calc(var(--spacing)*52)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-4{flex:4 4 8rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-4{width:calc(var(--spacing)*56);max-width:calc(var(--spacing)*64)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-5{flex:5 5 10rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-5{width:calc(var(--spacing)*68);max-width:calc(var(--spacing)*76)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-6{flex:6 6 12rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-6{width:calc(var(--spacing)*80);max-width:calc(var(--spacing)*88)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-7{flex:7 7 14rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-7{width:calc(var(--spacing)*92);max-width:calc(var(--spacing)*100)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-8{flex:8 8 16rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-8{width:calc(var(--spacing)*104);max-width:calc(var(--spacing)*112)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-9{flex:9 9 18rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-9{width:calc(var(--spacing)*116);max-width:calc(var(--spacing)*124)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-10{flex:0 0 100%;max-width:calc(100vw - 40px)}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-10{width:calc(var(--spacing)*128);max-width:calc(var(--spacing)*136)}}:is(table.table,.table) th,:is(table.table,.table) .th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);background-color:var(--label-surface);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);text-align:left;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}@media not all and (min-width:48rem){:is(table.table,.table) th,:is(table.table,.table) .th{border-style:var(--tw-border-style);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:0;flex:50%}}#cbi-samba4 :is(:is(table.table,.table) th,:is(table.table,.table) .th){padding-inline:calc(var(--spacing)*.5);padding-block:calc(var(--spacing)*1.5)}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[width="100%"]{width:100%}@media not all and (min-width:48rem){:is(:is(table.table,.table) th,:is(table.table,.table) .th)[width="100%"]{flex:0 0 100%}}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[width="33%"]{width:33.3333%}@media not all and (min-width:48rem){:is(:is(table.table,.table) th,:is(table.table,.table) .th)[width="33%"]{flex:0 0 33.333%}}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true]{cursor:pointer;-webkit-user-select:none;user-select:none}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true]:after{content:var(--tw-content);margin-left:calc(var(--spacing)*.5);content:var(--tw-content);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}@media (hover:hover){:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true]:hover{background-color:var(--label-surface)}}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true][data-sort-direction=asc]:after{--tw-content:"▲";content:var(--tw-content)}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true][data-sort-direction=desc]:after{--tw-content:"▼";content:var(--tw-content)}:is(table.table,.table)[id*=status_leases] .td{max-width:calc(var(--spacing)*62)}@media not all and (min-width:48rem){:is(table.table,.table)[id*=status_leases] .td{max-width:100%}}:is(table.table,.table) td,:is(table.table,.table) .td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);vertical-align:middle;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));overflow-wrap:break-word;white-space:normal;display:table-cell}@media not all and (min-width:48rem){:is(table.table,.table) td,:is(table.table,.table) .td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:0;flex:50%}}:is(:is(table.table,.table) td,:is(table.table,.table) .td):before{content:var(--tw-content);--tw-content:attr(data-title);content:var(--tw-content);content:var(--tw-content);display:none}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td):before{margin-bottom:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);display:block}}:is(:is(table.table,.table) td,:is(table.table,.table) .td):before{content:var(--tw-content)}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td):before{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}}#cbi-samba4 :is(:is(table.table,.table) td,:is(table.table,.table) .td){padding-inline:calc(var(--spacing)*.5);padding-block:calc(var(--spacing)*1.5)}[data-page=admin-system-mounts] :is(:is(table.table,.table) td,:is(table.table,.table) .td){max-width:calc(var(--spacing)*104)}@media not all and (min-width:48rem){[data-page=admin-system-mounts] :is(:is(table.table,.table) td,:is(table.table,.table) .td){max-width:100%}#wifi_assoclist_table :is(:is(table.table,.table) td,:is(table.table,.table) .td){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);flex:0 0 100%}}#wifi_assoclist_table :is(:is(table.table,.table) td,:is(table.table,.table) .td):before{content:var(--tw-content)}@media not all and (min-width:48rem){#wifi_assoclist_table :is(:is(table.table,.table) td,:is(table.table,.table) .td):before{margin-bottom:calc(var(--spacing)*1.5)}}:is(:is(table.table,.table) td,:is(table.table,.table) .td)>input[type=text],:is(:is(table.table,.table) td,:is(table.table,.table) .td)>input[type=password],:is(:is(table.table,.table) td,:is(table.table,.table) .td)>select,:is(:is(table.table,.table) td,:is(table.table,.table) .td)>.cbi-dropdown:not(.btn):not(.cbi-button){width:100%}:is(:is(table.table,.table) td,:is(table.table,.table) .td).cbi-value-field{word-break:break-all}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td).cbi-section-actions{flex:0 0 100%}:is(:is(table.table,.table) td,:is(table.table,.table) .td).cbi-section-actions>div{width:100%}:is(:is(table.table,.table) td,:is(table.table,.table) .td).cbi-section-actions>div>.cbi-button{flex:1}:is(:is(table.table,.table) td,:is(table.table,.table) .td)>.cbi-button{width:100%}}:is(:is(table.table,.table) td,:is(table.table,.table) .td)[width="100%"]{width:100%}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td)[width="100%"]{flex:0 0 100%}}:is(:is(table.table,.table) td,:is(table.table,.table) .td)[width="33%"]{width:33.3333%}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td)[width="33%"]{flex:0 0 33.333%}[data-page=admin-network-network] :is(:is(table.table,.table) td,:is(table.table,.table) .td)[data-name=_ifacebox],[data-page=admin-network-network] :is(:is(table.table,.table) td,:is(table.table,.table) .td)[data-name=_ifacestat]{flex-basis:100%}}.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell{width:auto!important}.cbi-input-invalid,.cbi-value-error{border-color:var(--error)!important}@supports (color:color-mix(in lab, red, red)){.cbi-input-invalid,.cbi-value-error{border-color:color-mix(in oklab,var(--error)70%,transparent)!important}}:is(.cbi-input-invalid,.cbi-value-error):focus{--tw-ring-color:var(--error)!important}@supports (color:color-mix(in lab, red, red)){:is(.cbi-input-invalid,.cbi-value-error):focus{--tw-ring-color:color-mix(in oklab,var(--error)40%,transparent)!important}}.cbi-value{gap:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*1.5);flex-flow:wrap;display:flex}@media not all and (min-width:48rem){.cbi-value{gap:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*1);flex-direction:column}}.cbi-value.hidden{display:none}.cbi-value+.cbi-value{margin-top:calc(var(--spacing)*3)}@media not all and (min-width:48rem){.cbi-value+.cbi-value{margin-top:calc(var(--spacing)*2)}}.cbi-value>.cbi-value-title{padding-top:calc(var(--spacing)*1);text-align:right;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}@media not all and (min-width:48rem){.cbi-value>.cbi-value-title{text-align:left;width:auto}}@media (min-width:48rem){.cbi-value>.cbi-value-title{flex:0 0 12rem}}.cbi-value>.cbi-value-field{flex:1;max-width:100%}.cbi-value>.cbi-value-field:has(>.cbi-dropdown[disabled]){cursor:not-allowed}.cbi-value>.cbi-value-field .cbi-value-description{margin-top:calc(var(--spacing)*1);padding-left:calc(var(--spacing)*4);--tw-leading:1;overflow-wrap:break-word;color:var(--muted-foreground);line-height:1;position:relative}.cbi-value>.cbi-value-field .cbi-value-description:not(:empty):before{content:var(--tw-content);content:var(--tw-content);left:calc(var(--spacing)*-.5);content:var(--tw-content);content:var(--tw-content);width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);content:var(--tw-content);background-color:var(--info-foreground);content:var(--tw-content);display:inline-block;position:absolute;-webkit-mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M15%208A7%207%200%201%201%201%208a7%207%200%200%201%2014%200M9%205a1%201%200%201%201-2%200a1%201%200%200%201%202%200M6.75%208a.75.75%200%200%200%200%201.5h.75v1.75a.75.75%200%200%200%201.5%200v-2.5A.75.75%200%200%200%208.25%208z'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M15%208A7%207%200%201%201%201%208a7%207%200%200%201%2014%200M9%205a1%201%200%201%201-2%200a1%201%200%200%201%202%200M6.75%208a.75.75%200%200%200%200%201.5h.75v1.75a.75.75%200%200%200%201.5%200v-2.5A.75.75%200%200%200%208.25%208z'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat}.cbi-value>.cbi-section,.cbi-value>.cbi-tblsection{width:100%}.cbi-dynlist{width:max-content;max-width:calc(var(--spacing)*120);align-items:flex-start;gap:calc(var(--spacing)*3);flex-direction:column;display:inline-flex}@media not all and (min-width:48rem){.cbi-dynlist{max-width:100%!important}}.cbi-dynlist .item{pointer-events:auto;cursor:move;align-items:flex-start;gap:calc(var(--spacing)*2);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);flex-direction:column;align-self:stretch;display:inline-flex;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.cbi-dynlist .item{border-color:color-mix(in oklab,var(--border)40%,transparent)}}.cbi-dynlist .item{background-color:var(--panel-bg);padding-block:calc(var(--spacing)*3);padding-right:calc(var(--spacing)*10);padding-left:calc(var(--spacing)*4);word-break:break-all;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);-webkit-user-select:text;user-select:text}@media not all and (min-width:48rem){.cbi-dynlist .item{width:100%;padding-block:calc(var(--spacing)*2.5);padding-right:calc(var(--spacing)*8);padding-left:calc(var(--spacing)*3)}}.cbi-dynlist .item:after{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*2.5);content:var(--tw-content);right:calc(var(--spacing)*2);content:var(--tw-content);content:var(--tw-content);height:calc(var(--spacing)*6);content:var(--tw-content);width:calc(var(--spacing)*6);content:var(--tw-content);content:var(--tw-content);cursor:pointer;content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);border-radius:calc(var(--radius-base)*1.5);content:var(--tw-content);background-color:var(--muted);content:var(--tw-content);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));content:var(--tw-content);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);content:var(--tw-content);color:var(--muted-foreground);--tw-content:"×";content:var(--tw-content);flex-shrink:0;justify-content:center;align-items:center;display:inline-flex;position:absolute}.cbi-dynlist .item.dragging{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y);cursor:grabbing;opacity:.5;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.cbi-dynlist .item.drag-over{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y);border-color:color-mix(in oklab,var(--primary)60%,transparent);background-color:color-mix(in oklab,var(--primary)5%,transparent);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--primary)30%,transparent)}.cbi-dynlist .item>span,.cbi-dynlist .item>code{pointer-events:none;border-radius:var(--radius-base);width:100%;font-family:var(--font-mono);--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.cbi-dynlist .placeholder{word-break:break-all}.cbi-dynlist .add-item{align-items:center;gap:calc(var(--spacing)*1.5);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);--tw-border-style:dashed;border-style:dashed;border-width:1px;border-color:var(--border);align-self:stretch;display:inline-flex}@supports (color:color-mix(in lab, red, red)){.cbi-dynlist .add-item{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.cbi-dynlist .add-item{padding-inline:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*1);background-color:#0000}@media (hover:hover){.cbi-dynlist .add-item:hover{border-color:var(--border);background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){.cbi-dynlist .add-item:hover{background-color:color-mix(in oklab,var(--page-bg)50%,transparent)}}}.cbi-dynlist .add-item>input{min-width:calc(var(--spacing)*61);flex:1}.cbi-dynlist .add-item>.cbi-button{min-height:calc(var(--spacing)*6);min-width:calc(var(--spacing)*6);cursor:pointer;border-radius:calc(var(--radius-base)*1.5);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*0);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);flex-shrink:0;justify-content:center;align-items:center;display:inline-flex}.cbi-dynlist .add-item>.cbi-button:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.cbi-tabmenu{margin-bottom:calc(var(--spacing)*1);align-items:center;gap:calc(var(--spacing)*1);border-radius:calc(var(--radius-base)*4);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);display:flex;overflow-x:auto}@supports (color:color-mix(in lab, red, red)){.cbi-tabmenu{border-color:color-mix(in oklab,var(--border)60%,transparent)}}.cbi-tabmenu{background-color:var(--label-surface);padding:calc(var(--spacing)*1)}.cbi-tabmenu li{margin:calc(var(--spacing)*0);flex-shrink:0;min-width:fit-content;list-style-type:none;position:relative}.cbi-tabmenu li[data-errors]:after{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*-.5);content:var(--tw-content);right:calc(var(--spacing)*-1.5);content:var(--tw-content);z-index:20;content:var(--tw-content);content:var(--tw-content);min-height:calc(var(--spacing)*4);content:var(--tw-content);min-width:calc(var(--spacing)*4);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);background-color:var(--accent);content:var(--tw-content);padding-inline:calc(var(--spacing)*1.5);content:var(--tw-content);padding-block:calc(var(--spacing)*.5);content:var(--tw-content);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));content:var(--tw-content);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);content:var(--tw-content);color:var(--accent-foreground);--tw-content:attr(data-errors);content:var(--tw-content);border-radius:3.40282e38px;justify-content:center;align-items:center;display:inline-flex;position:absolute}.cbi-tabmenu li[data-errors]>a{border-style:var(--tw-border-style);border-width:1px;border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.cbi-tabmenu li[data-errors]>a{border-color:color-mix(in oklab,var(--error)50%,transparent)}}.cbi-tabmenu li>a{border-radius:calc(var(--radius-base)*4);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--muted-foreground);text-decoration-line:none;display:block}@media (hover:hover){.cbi-tabmenu li>a:hover{color:var(--foreground)}}.cbi-tabmenu li.cbi-tab a{background-color:var(--foreground);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--page-bg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[data-tab-title]{visibility:hidden;height:calc(var(--spacing)*0);overflow:hidden}[data-tab-active=true]{visibility:visible;height:auto;overflow:visible}#tabmenu{margin-bottom:calc(var(--spacing)*8);width:100%}.tabs{align-items:center;gap:calc(var(--spacing)*0);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);display:flex;position:relative;overflow-x:auto}.tabs a{padding-inline:calc(var(--spacing)*8);padding-block:calc(var(--spacing)*4);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--muted-foreground);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s;display:block}@media (hover:hover){.tabs a:hover{color:var(--foreground);text-decoration-line:none}}.tabs>li.active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--foreground)}.tabs>li.active>a{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--foreground)}.cbi-section-node-tabbed{margin-top:calc(var(--spacing)*6)}@media (min-width:48rem){.left{text-align:left!important}}.left:not(td){text-align:left!important}@media (min-width:48rem){.right{text-align:right!important}}.right:not(td){text-align:right!important}@media (min-width:48rem){.top{vertical-align:top!important}}.top:not(td){vertical-align:top!important}@media (min-width:48rem){.bottom{vertical-align:bottom!important}}.bottom:not(td){vertical-align:bottom!important}@media (min-width:48rem){.center{text-align:center!important}}.center:not(td){text-align:center!important}@media (min-width:48rem){.middle{vertical-align:middle!important}}.middle:not(td){vertical-align:middle!important}@media (min-width:48rem){.nowrap:not(span){white-space:nowrap}}.nowrap:not(span):not(td){white-space:nowrap}.spinning{display:inline-block;position:relative;padding-left:calc(var(--spacing)*8)!important}.spinning:before{content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);content:var(--tw-content);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);content:var(--tw-content);animation:var(--animate-spin);content:var(--tw-content);content:var(--tw-content);background-color:currentColor;position:absolute;top:50%;left:6px;-webkit-mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1758357645269'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='7610'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M512%2085.333333c235.648%200%20426.666667%20191.018667%20426.666667%20426.666667s-191.018667%20426.666667-426.666667%20426.666667S85.333333%20747.648%2085.333333%20512%20276.352%2085.333333%20512%2085.333333z%20m0%20128a298.666667%20298.666667%200%201%200%200%20597.333334%20298.666667%20298.666667%200%200%200%200-597.333334z'%20fill='%23000000'%20fill-opacity='.05'%20p-id='7611'%3e%3c/path%3e%3cpath%20d='M813.696%20813.696c166.613333-166.613333%20166.613333-436.778667%200-603.392-166.613333-166.613333-436.778667-166.613333-603.392%200A64%2064%200%200%200%20300.8%20300.8a298.666667%20298.666667%200%201%201%20422.4%20422.4%2064%2064%200%200%200%2090.496%2090.496z'%20fill='%23000000'%20p-id='7612'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1758357645269'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='7610'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M512%2085.333333c235.648%200%20426.666667%20191.018667%20426.666667%20426.666667s-191.018667%20426.666667-426.666667%20426.666667S85.333333%20747.648%2085.333333%20512%20276.352%2085.333333%20512%2085.333333z%20m0%20128a298.666667%20298.666667%200%201%200%200%20597.333334%20298.666667%20298.666667%200%200%200%200-597.333334z'%20fill='%23000000'%20fill-opacity='.05'%20p-id='7611'%3e%3c/path%3e%3cpath%20d='M813.696%20813.696c166.613333-166.613333%20166.613333-436.778667%200-603.392-166.613333-166.613333-436.778667-166.613333-603.392%200A64%2064%200%200%200%20300.8%20300.8a298.666667%20298.666667%200%201%201%20422.4%20422.4%2064%2064%200%200%200%2090.496%2090.496z'%20fill='%23000000'%20p-id='7612'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat}#view>.spinning:first-child{min-height:calc(var(--spacing)*48);place-items:center;width:fit-content;margin-inline:auto;display:grid}@media not all and (min-width:48rem){#view>.spinning:first-child{min-height:calc(100dvh - 4rem)}.diag-style{gap:calc(var(--spacing)*4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}}.diag-style>div{align-items:center;gap:calc(var(--spacing)*2);flex-direction:column;display:flex}@media not all and (min-width:48rem){.diag-style>div{gap:calc(var(--spacing)*1);width:100%!important}}#file-manager-container{overflow:auto}#file-manager-container #status-bar{border-style:var(--tw-border-style);background-color:var(--page-bg);border-width:0}[data-page=admin-statistics-graphs] [data-plugin] img:where([data-darkmode=true],[data-darkmode=true] *){--tw-hue-rotate:hue-rotate(150deg);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,);--tw-invert:invert(100%)}@media not all and (min-width:48rem){[data-name=bridge-vlan]>div{overflow:visible!important}}[data-page=admin-services-openclash-config] .sub_div img{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}@media not all and (min-width:48rem){[data-page=admin-services-openclash-settings] .cbi-value-field .cbi-input-select{width:100%!important}}.Dashboard>.section-content .dashboard-bg.tr{border-radius:0}.Dashboard>.section-content .dashboard-bg{background-color:var(--panel-bg)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg{background-color:color-mix(in oklab,var(--panel-bg)95%,transparent)}}.Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg:not(.tr){border-color:color-mix(in oklab,var(--border)40%,transparent)}}.Dashboard>.section-content .dashboard-bg:not(.tr){--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){.Dashboard>.section-content .dashboard-bg:not(.tr):hover{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg:not(.tr):hover{border-color:color-mix(in oklab,var(--border)50%,transparent)}}.Dashboard>.section-content .dashboard-bg:not(.tr):hover{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){.Dashboard>.section-content .dashboard-bg{background-color:var(--panel-bg)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg{background-color:color-mix(in oklab,var(--panel-bg)98%,transparent)}}.Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg:not(.tr){border-color:color-mix(in oklab,var(--border)30%,transparent)}}}.Dashboard>.section-content .dashboard-bg span:where([data-darkmode=true],[data-darkmode=true] *),.Dashboard>.section-content .dashboard-bg td:where([data-darkmode=true],[data-darkmode=true] *),.Dashboard>.section-content .dashboard-bg th:where([data-darkmode=true],[data-darkmode=true] *){color:var(--foreground)!important}.Dashboard>.section-content .dashboard-bg img[src*=\.svg]:where([data-darkmode=true],[data-darkmode=true] *){--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*2)}@media not all and (min-width:48rem){.Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*1)}}.Dashboard>.section-content .dashboard-bg .th{padding-block:calc(var(--spacing)*2)}.Dashboard>.section-content .dashboard-bg .table:not(.assoclist){margin-bottom:calc(var(--spacing)*0);border-style:var(--tw-border-style);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;border-width:0;border-radius:0}@media (hover:hover){.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr:hover{background-color:#0000}}@media not all and (min-width:48rem){.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*0);border-width:0;display:table-row}}.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*.5);border-width:0}@media not all and (min-width:48rem){.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{display:table-cell}}.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td:first-child{padding-right:calc(var(--spacing)*2);white-space:nowrap;opacity:.7}.Dashboard>.section-content .title h3{border-style:var(--tw-border-style);padding-bottom:calc(var(--spacing)*4);color:var(--foreground);border-width:0}@media not all and (min-width:48rem){.Dashboard>.section-content .title h3{margin-inline:calc(var(--spacing)*0);padding-bottom:calc(var(--spacing)*2)}}.Dashboard>.section-content .router-status-wifi .wifi-info .devices-info .tr .td:nth-child(3){top:calc(var(--spacing)*0);width:100%;padding-inline:calc(var(--spacing)*0);position:relative}@media not all and (min-width:48rem){.Dashboard>.section-content .router-status-wifi .wifi-info .devices-info .tr .td{flex:50%}}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-spacing-x{syntax:"";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"";inherits:false;initial-value:0}@keyframes spin{to{transform:rotate(360deg)}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}} +@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-font-weight:initial;--tw-duration:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-space-y-reverse:0;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-2xl:calc(var(--radius-base)*2);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{scrollbar-color:initial;scrollbar-width:initial}.collapse{visibility:collapse}.fixed{position:fixed}.static{position:static}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.scrollbar::-webkit-scrollbar-track{background-color:var(--scrollbar-track);border-radius:var(--scrollbar-track-radius)}.scrollbar::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);border-radius:var(--scrollbar-thumb-radius)}.scrollbar::-webkit-scrollbar-corner{background-color:var(--scrollbar-corner);border-radius:var(--scrollbar-corner-radius)}.scrollbar{scrollbar-width:auto;scrollbar-color:var(--scrollbar-thumb,initial)var(--scrollbar-track,initial)}.scrollbar::-webkit-scrollbar{width:var(--scrollbar-width,16px);height:var(--scrollbar-height,16px);display:block}.hidden{display:none}.table{display:table}.rounded{border-radius:calc(var(--radius-base)*.5)}.underline{text-decoration-line:underline}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--background:#f1f5f9;--foreground:#0f172b;--primary:#46a3d1;--primary-foreground:#fff;--secondary:#e2e8f0;--secondary-foreground:#314158;--muted:#f5f5f5;--muted-foreground:#45556c;--destructive:#ffe2df;--destructive-foreground:#6c1517;--accent:#ee343b;--accent-foreground:#fff1f0;--default:#f5f5f5;--default-foreground:#171717;--info:#d3f1ff;--info-foreground:#003f60;--warning:#fbeec9;--warning-foreground:#582f02;--success:#cff6e0;--success-foreground:#003d2a;--error:#ffe2df;--error-foreground:#6c1517;--border:#e4e4e4;--link:#ec6cff;--input-checked:#fff;--label-surface:oklch(from var(--panel-bg)calc(l - .02)c h);--login-aurora-1:#00c23d;--login-aurora-2:#00a6a0;--login-aurora-3:#4266ff;--login-aurora-4:#b93ef3;--terminal-bg:#1a2029;--terminal-foreground:#fff;--overlay-base:#000;--progress-bar-start:#46a3d1;--progress-bar-end:#4fc3c7;--header-bg:oklch(from var(--background)l c h);--header-interactive:oklch(from var(--header-bg)calc(l - .12)c h);--page-bg:#fff;--panel-bg:#fff;--tooltip-bg:oklch(from var(--page-bg)calc(l - .015)c h);--font-sans:"Lato",ui-sans-serif,system-ui,sans-serif;--font-mono:ui-monospace,"SF Mono",Menlo,Monaco,Consolas,monospace;--spacing:.25rem;--container-max-width:80rem;--radius-base:.5rem}@supports (color:lab(0% 0 0)){:root{--background:lab(96.286% -.852436 -2.46847);--foreground:lab(7.78673% 1.82345 -15.0537);--primary:lab(63.0437% -17.6746 -32.0381);--primary-foreground:lab(100% 0 0);--secondary:lab(91.7353% -.998765 -4.76968);--secondary-foreground:lab(26.9569% -1.47016 -15.6993);--muted:lab(96.52% -.0000298023 .0000119209);--muted-foreground:lab(35.5623% -1.74978 -15.4316);--destructive:lab(92.5749% 15.3735 8.32583);--destructive-foreground:lab(23.385% 37.9736 23.5817);--accent:lab(53.6853% 69.6775 43.7724);--accent-foreground:lab(96.3359% 6.12152 3.29586);--default:lab(96.52% -.0000298023 .0000119209);--default-foreground:lab(7.78201% -.0000149012 0);--info:lab(93.2141% -10.1735 -14.3943);--info-foreground:lab(24.4811% -7.8918 -24.9024);--warning:lab(94.421% .545532 19.5962);--warning-foreground:lab(24.2528% 17.0621 33.1082);--success:lab(93.715% -16.1219 6.39679);--success-foreground:lab(22.3041% -31.6825 8.84454);--error:lab(92.5749% 15.3735 8.32583);--error-foreground:lab(23.385% 37.9736 23.5817);--border:lab(90.72% .0000298023 -.0000119209);--link:lab(66.1178% 66.0652 -52.4733);--input-checked:lab(100% 0 0);--login-aurora-1:lab(68.2738% -77.861 60.3677);--login-aurora-2:lab(61.8807% -76.0735 -14.3835);--login-aurora-3:lab(47.9708% 31.2682 -84.9353);--login-aurora-4:lab(51.406% 68.7947 -68.8447);--terminal-bg:lab(11.7506% -.5823 -7.27103);--terminal-foreground:lab(100% 0 0);--overlay-base:lab(0% 0 0);--progress-bar-start:lab(63.0437% -17.6746 -32.0381);--progress-bar-end:lab(72.4424% -32.9195 -12.3464);--page-bg:lab(100% 0 0);--panel-bg:lab(100% 0 0)}}[data-darkmode=true]{--background:#003c38;--foreground:#f1f5f9;--primary:#00958e;--primary-foreground:#fff;--secondary:#314158;--secondary-foreground:#e5e7eb;--muted:#38414f;--muted-foreground:#90a1b9;--destructive:#460809;--destructive-foreground:#ffc6c0;--accent:#6c1517;--accent-foreground:#ffc6c0;--default:#27272a80;--default-foreground:#f9f9ff;--info:#024a7080;--info-foreground:#b0e0f8;--warning:#7b330680;--warning-foreground:#fee685;--success:#004e3b80;--success-foreground:#aff8d0;--error:#8b083680;--error-foreground:#ffc6c0;--border:#364358;--link:#a685ff;--input-checked:#fff;--label-surface:oklch(from var(--panel-bg)calc(l + .04)c h);--login-aurora-1:#00d847;--login-aurora-2:#00b7b3;--login-aurora-3:#5279ff;--login-aurora-4:#c854ff;--terminal-bg:#0a121f;--terminal-foreground:#fff;--overlay-base:#000;--progress-bar-start:#065f46;--progress-bar-end:#009b94;--header-bg:oklch(from var(--page-bg)l c h);--header-interactive:oklch(from var(--header-bg)calc(l + .24)c h);--page-bg:#101828;--panel-bg:#1d293d;--tooltip-bg:oklch(from var(--page-bg)calc(l + .06)c h)}@supports (color:lab(0% 0 0)){[data-darkmode=true]{--background:lab(21.8032% -20.1655 -3.35968);--foreground:lab(96.286% -.852436 -2.46847);--primary:lab(55.0978% -44.5567 -7.62978);--primary-foreground:lab(100% 0 0);--secondary:lab(26.9569% -1.47016 -15.6993);--secondary-foreground:lab(91.6229% -.159115 -2.26791);--muted:lab(27.1597% -.911355 -9.48523);--muted-foreground:lab(65.5349% -2.25151 -14.5072);--destructive:lab(13.003% 29.04 16.7519);--destructive-foreground:lab(84.7556% 43.3341 25.2722);--accent:lab(23.385% 37.9736 23.5817);--accent-foreground:lab(84.7347% 43.595 24.3042);--default:lab(15.7305% .613764 -2.16959/.5);--default-foreground:lab(98.1732% .982702 -3.63715);--info:lab(29.1959% -8.34689 -28.2453/.5);--info-foreground:lab(86.2797% -12.0086 -17.1909);--warning:lab(31.2288% 30.2627 40.0378/.5);--warning-foreground:lab(91.7203% -.505269 49.9084);--success:lab(28.8637% -26.9249 5.45986/.5);--success-foreground:lab(91.9265% -29.2737 11.5846);--error:lab(29.7104% 51.514 12.6253/.5);--error-foreground:lab(84.7347% 43.595 24.3042);--border:lab(27.8781% -.856712 -14.804);--link:lab(62.8239% 34.9159 -60.0512);--input-checked:lab(100% 0 0);--login-aurora-1:lab(75.4735% -83.8096 64.8452);--login-aurora-2:lab(67.7176% -77.9628 -17.8815);--login-aurora-3:lab(53.7474% 31.0421 -88.4899);--login-aurora-4:lab(57.0971% 71.0297 -71.531);--terminal-bg:lab(5.13221% -.0256523 -9.94164);--terminal-foreground:lab(100% 0 0);--overlay-base:lab(0% 0 0);--progress-bar-start:lab(35.2336% -29.9851 7.30804);--progress-bar-end:lab(57.6867% -49.5425 -9.29472);--page-bg:lab(8.11897% .811279 -12.254);--panel-bg:lab(16.132% -.318035 -14.6672)}}:not(html)::-webkit-scrollbar-track{background-color:var(--scrollbar-track);border-radius:var(--scrollbar-track-radius)}:not(html)::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);border-radius:var(--scrollbar-thumb-radius)}:not(html)::-webkit-scrollbar-corner{background-color:var(--scrollbar-corner);border-radius:var(--scrollbar-corner-radius)}:not(html){scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb,initial)var(--scrollbar-track,initial)}:not(html)::-webkit-scrollbar{width:8px;height:8px;display:block}:not(html){--scrollbar-thumb:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:not(html){--scrollbar-thumb:color-mix(in oklab,var(--foreground)70%,transparent)}}:not(html){--scrollbar-thumb-radius:9999px;--scrollbar-track:transparent}html::-webkit-scrollbar-track{background-color:var(--scrollbar-track);border-radius:var(--scrollbar-track-radius)}html::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);border-radius:var(--scrollbar-thumb-radius)}html::-webkit-scrollbar-corner{background-color:var(--scrollbar-corner);border-radius:var(--scrollbar-corner-radius)}html{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb,initial)var(--scrollbar-track,initial)}html::-webkit-scrollbar{width:8px;height:8px;display:block}html{--scrollbar-thumb:var(--foreground)}@supports (color:color-mix(in lab, red, red)){html{--scrollbar-thumb:color-mix(in oklab,var(--foreground)70%,transparent)}}html{--scrollbar-thumb-radius:9999px;--scrollbar-track:var(--page-bg);background-color:var(--background);height:100%;font-family:var(--font-sans);position:relative}html body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--foreground);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;background-color:#0000;flex-direction:column;transition-duration:.3s;display:flex;position:relative}html body.modal-overlay-active{height:100vh;overflow:hidden}h1,h2,h3,h4,h5,h6{font-family:var(--font-sans);--tw-leading:var(--leading-tight);line-height:var(--leading-tight);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--foreground)}h1{margin-bottom:calc(var(--spacing)*4);padding-left:calc(var(--spacing)*6);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}@media not all and (min-width:48rem){h1{padding-left:calc(var(--spacing)*4);font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}}h2{margin-bottom:calc(var(--spacing)*4);padding-left:calc(var(--spacing)*5);font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}@media not all and (min-width:48rem){h2{margin-bottom:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*4);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}h3{margin-bottom:calc(var(--spacing)*4);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}@media not all and (min-width:48rem){h3{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}h4{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}@media not all and (min-width:48rem){h4{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}h5{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}@media not all and (min-width:48rem){h5{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}h6{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}@media not all and (min-width:48rem){h6{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}hr{border-style:var(--tw-border-style);border-width:0}strong{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}abbr{cursor:help}p{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--default-foreground)}pre{border-radius:calc(var(--radius-base)*1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--secondary);display:block}@supports (color:color-mix(in lab, red, red)){pre{border-color:color-mix(in oklab,var(--secondary)60%,transparent)}}pre{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){pre{background-color:color-mix(in oklab,var(--secondary)60%,transparent)}}pre{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);overflow-wrap:break-word;white-space:pre-wrap;color:var(--secondary-foreground)}code{border-radius:calc(var(--radius-base)*.5);background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){code{background-color:color-mix(in oklab,var(--secondary)80%,transparent)}}code{padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1);font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--secondary-foreground)}a{color:var(--link);text-decoration-line:none}@media (hover:hover){a:hover{text-decoration-line:underline}}var{font-family:var(--font-mono);color:var(--link)}small{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--default-foreground)}em{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--muted-foreground);font-style:italic}header{top:calc(var(--spacing)*0);z-index:60;margin-bottom:calc(var(--spacing)*2);background-color:var(--header-bg);position:sticky}header .header-content{height:calc(var(--spacing)*14);padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*3);justify-content:space-between;align-items:center;display:flex;position:relative}@media not all and (min-width:48rem){header .header-content{padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2)}}[data-nav-type=mega-menu] :is(header) .desktop-menu-container{pointer-events:none;inset-inline:calc(var(--spacing)*0);top:calc(var(--spacing)*0);z-index:30;height:calc(var(--spacing)*0);background-color:var(--header-bg);width:100%;position:absolute;overflow:hidden}@supports (color:color-mix(in lab, red, red)){[data-nav-type=mega-menu] :is(header) .desktop-menu-container{background-color:color-mix(in oklab,var(--header-bg)40%,transparent)}}[data-nav-type=mega-menu] :is(header) .desktop-menu-container{opacity:0;--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;--tw-ease:var(--ease-in-out);transition-duration:.15s;transition-timing-function:var(--ease-in-out)}@media not all and (min-width:48rem){[data-nav-type=mega-menu] :is(header) .desktop-menu-container{display:none}}[data-nav-type=mega-menu] :is(header) .desktop-menu-container:where([data-darkmode=true],[data-darkmode=true] *){background-color:var(--header-bg)}@supports (color:color-mix(in lab, red, red)){[data-nav-type=mega-menu] :is(header) .desktop-menu-container:where([data-darkmode=true],[data-darkmode=true] *){background-color:color-mix(in oklab,var(--header-bg)90%,transparent)}}[data-nav-type=mega-menu] :is(header) .desktop-menu-container.active{pointer-events:auto;height:var(--mega-menu-height,0);opacity:1}header .brand{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--foreground);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;text-decoration-line:none;transition-duration:.2s;display:inline-block}@media (hover:hover){header .brand:hover{--tw-translate-y:calc(var(--spacing)*-.5);translate:var(--tw-translate-x)var(--tw-translate-y);color:var(--primary)}}@media not all and (min-width:48rem){header .brand{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));flex:1;order:1}}header .nav{z-index:60;margin:calc(var(--spacing)*0);--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);align-items:center;gap:calc(var(--spacing)*1);padding:calc(var(--spacing)*0);list-style-type:none;display:flex;position:absolute;left:50%}@media not all and (min-width:48rem){header .nav{display:none}}header .nav>li{position:relative}header .nav>li .menu{border-radius:calc(var(--radius-base)*1.5);padding-inline:calc(var(--spacing)*3.5);padding-block:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--foreground);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;text-decoration-line:none;transition-duration:.15s;display:block}header .nav>li .menu.menu-active{color:var(--foreground)}[data-nav-type=mega-menu] header .nav>li .menu.menu-active{text-underline-offset:4px;text-decoration-line:underline;text-decoration-thickness:2px}[data-nav-type=boxed-dropdown] header .nav>li .menu.menu-active{background-color:var(--header-interactive)}header .nav>li .desktop-nav{pointer-events:none;left:calc(var(--spacing)*0);z-index:40;padding-inline:calc(var(--spacing)*0);padding-top:calc(var(--spacing)*0);opacity:0;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;transition-duration:.15s}[data-nav-type=mega-menu] :is(header .nav>li .desktop-nav){top:calc(var(--spacing)*14);--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);padding-bottom:calc(var(--spacing)*6);position:fixed;left:50%}[data-nav-type=boxed-dropdown] :is(header .nav>li .desktop-nav){margin-top:calc(var(--spacing)*2);min-width:calc(var(--spacing)*48);position:absolute;top:100%}header .nav>li .desktop-nav.active{pointer-events:auto;opacity:1}[data-nav-type=mega-menu] :is(header .nav>li .desktop-nav .desktop-nav-list){column-gap:calc(var(--spacing)*4);row-gap:calc(var(--spacing)*2);grid-template-columns:repeat(4,minmax(0,1fr));width:max-content;display:grid}[data-nav-type=boxed-dropdown] :is(header .nav>li .desktop-nav .desktop-nav-list){row-gap:calc(var(--spacing)*1);border-radius:calc(var(--radius-base)*3);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:color-mix(in oklab,var(--header-bg)90%,transparent);padding-block:calc(var(--spacing)*2);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-md));--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-direction:column;transition-duration:.2s;display:flex}header .nav>li .desktop-nav .desktop-nav-list>li{margin:calc(var(--spacing)*0);list-style-type:none}header .nav>li .desktop-nav .desktop-nav-list>li>a{padding-inline:calc(var(--spacing)*4);white-space:nowrap;color:var(--foreground);text-decoration-line:none;display:block}[data-nav-type=mega-menu] :is(header .nav>li .desktop-nav .desktop-nav-list>li>a){padding-block:calc(var(--spacing)*3)}@media (hover:hover){[data-nav-type=mega-menu] :is(header .nav>li .desktop-nav .desktop-nav-list>li>a):hover{text-underline-offset:4px;text-decoration-line:underline;text-decoration-thickness:2px}}[data-nav-type=boxed-dropdown] :is(header .nav>li .desktop-nav .desktop-nav-list>li>a){margin-inline:calc(var(--spacing)*2);border-radius:calc(var(--radius-base)*1.5);padding-block:calc(var(--spacing)*2)}@media (hover:hover){[data-nav-type=boxed-dropdown] :is(header .nav>li .desktop-nav .desktop-nav-list>li>a):hover{background-color:var(--header-interactive)}}@media not all and (min-width:48rem){header .mobile-controls{flex-shrink:0;order:9999;align-items:center;display:flex}}@media (min-width:48rem){header .mobile-controls{display:none}}@media not all and (min-width:48rem){header .mobile-controls .mobile-menu-toggle{margin-left:calc(var(--spacing)*5);cursor:pointer;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}@media (hover:hover){header .mobile-controls .mobile-menu-toggle:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}}header .mobile-controls .mobile-menu-toggle:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}}header .mobile-controls .mobile-menu-toggle svg{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}@media not all and (min-width:48rem){header .mobile-controls .mobile-menu-toggle svg{color:var(--foreground);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}header .mobile-controls .mobile-menu-toggle.active svg{rotate:180deg}}header #indicators{gap:calc(var(--spacing)*5);flex-direction:row-reverse;flex-shrink:0;display:flex}@media not all and (min-width:48rem){header #indicators{flex-shrink:0;order:2}}@media (min-width:48rem){header #indicators{gap:calc(var(--spacing)*8)}}header #indicators span[data-indicator]{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);cursor:pointer;font-size:0}header #indicators span[data-indicator]:before{content:var(--tw-content);content:var(--tw-content);width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);content:var(--tw-content);content:var(--tw-content);color:var(--foreground);background-color:currentColor;position:absolute}header #indicators span[data-indicator][data-indicator=media_error]:before{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M99.5149%20622.549C93.2144%20633.653%2093.4349%20634%20107.075%20634H651.912C665.537%20634%20665.757%20633.622%20659.473%20622.549L385.196%20138.236C378.99%20127.258%20379.998%20127.258%20373.792%20138.236L99.5149%20622.534V622.549ZM440.01%20107.161L714.303%20591.475C744.577%20644.978%20713.373%20697%20651.912%20697H107.075C45.5668%20697%2014.4423%20644.946%2044.7005%20591.506L318.977%20107.192C349.314%2053.5947%20409.705%2053.6105%20440.026%20107.192L440.01%20107.161ZM379.494%20586.75C383.631%20586.75%20387.728%20585.936%20391.551%20584.353C395.373%20582.77%20398.847%20580.45%20401.773%20577.525C404.698%20574.6%20407.019%20571.127%20408.603%20567.305C410.186%20563.483%20411.001%20559.387%20411.001%20555.25C411.001%20551.112%20410.186%20547.016%20408.603%20543.194C407.019%20539.372%20404.698%20535.899%20401.773%20532.974C398.847%20530.049%20395.373%20527.729%20391.551%20526.146C387.728%20524.563%20383.631%20523.749%20379.494%20523.749C371.14%20523.751%20363.128%20527.07%20357.221%20532.977C351.314%20538.885%20347.996%20546.896%20347.996%20555.25C347.996%20563.603%20351.314%20571.614%20357.221%20577.522C363.128%20583.429%20371.14%20586.748%20379.494%20586.75ZM347.991%20303.249V460.749C347.991%20469.104%20351.31%20477.116%20357.218%20483.023C363.126%20488.931%20371.139%20492.249%20379.494%20492.249C387.849%20492.249%20395.862%20488.931%20401.769%20483.023C407.677%20477.116%20410.996%20469.104%20410.996%20460.749V303.249C410.996%20294.894%20407.677%20286.882%20401.769%20280.975C395.862%20275.067%20387.849%20271.749%20379.494%20271.749C371.139%20271.749%20363.126%20275.067%20357.218%20280.975C351.31%20286.882%20347.991%20294.894%20347.991%20303.249Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M99.5149%20622.549C93.2144%20633.653%2093.4349%20634%20107.075%20634H651.912C665.537%20634%20665.757%20633.622%20659.473%20622.549L385.196%20138.236C378.99%20127.258%20379.998%20127.258%20373.792%20138.236L99.5149%20622.534V622.549ZM440.01%20107.161L714.303%20591.475C744.577%20644.978%20713.373%20697%20651.912%20697H107.075C45.5668%20697%2014.4423%20644.946%2044.7005%20591.506L318.977%20107.192C349.314%2053.5947%20409.705%2053.6105%20440.026%20107.192L440.01%20107.161ZM379.494%20586.75C383.631%20586.75%20387.728%20585.936%20391.551%20584.353C395.373%20582.77%20398.847%20580.45%20401.773%20577.525C404.698%20574.6%20407.019%20571.127%20408.603%20567.305C410.186%20563.483%20411.001%20559.387%20411.001%20555.25C411.001%20551.112%20410.186%20547.016%20408.603%20543.194C407.019%20539.372%20404.698%20535.899%20401.773%20532.974C398.847%20530.049%20395.373%20527.729%20391.551%20526.146C387.728%20524.563%20383.631%20523.749%20379.494%20523.749C371.14%20523.751%20363.128%20527.07%20357.221%20532.977C351.314%20538.885%20347.996%20546.896%20347.996%20555.25C347.996%20563.603%20351.314%20571.614%20357.221%20577.522C363.128%20583.429%20371.14%20586.748%20379.494%20586.75ZM347.991%20303.249V460.749C347.991%20469.104%20351.31%20477.116%20357.218%20483.023C363.126%20488.931%20371.139%20492.249%20379.494%20492.249C387.849%20492.249%20395.862%20488.931%20401.769%20483.023C407.677%20477.116%20410.996%20469.104%20410.996%20460.749V303.249C410.996%20294.894%20407.677%20286.882%20401.769%20280.975C395.862%20275.067%20387.849%20271.749%20379.494%20271.749C371.139%20271.749%20363.126%20275.067%20357.218%20280.975C351.31%20286.882%20347.991%20294.894%20347.991%20303.249Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat}header #indicators span[data-indicator][data-indicator=poll-status][data-style=active]:before{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M201.032%20123.921C202.67%20122.913%20204.182%20121.936%20205.537%20121.023C257.614%2085.7368%20319.094%2066.9154%20382%2067.0003C555.974%2067.0003%20697%20208.026%20697%20382C697.086%20440.93%20680.568%20498.691%20649.34%20548.666C648.405%20550.159%20647.097%20551.383%20645.545%20552.216C643.993%20553.05%20642.25%20553.465%20640.489%20553.42C638.728%20553.376%20637.009%20552.873%20635.501%20551.962C633.993%20551.051%20632.748%20549.763%20631.889%20548.225L552.509%20405.404C551.176%20403.007%20550.492%20400.303%20550.525%20397.56C550.558%20394.817%20551.307%20392.13%20552.698%20389.765C554.089%20387.401%20556.073%20385.44%20558.454%20384.078C560.836%20382.716%20563.531%20382%20566.275%20382H634C634.01%20335.412%20621.106%20289.734%20596.72%20250.038C572.334%20210.343%20537.422%20178.184%20495.861%20157.135C454.3%20136.085%20407.718%20126.968%20361.288%20130.797C314.858%20134.626%20270.398%20151.251%20232.847%20178.825C229.627%20181.188%20225.94%20182.839%20222.032%20183.668C218.124%20184.497%20214.085%20184.485%20210.182%20183.633C206.279%20182.781%20202.602%20181.108%20199.395%20178.726C196.189%20176.344%20193.525%20173.307%20191.582%20169.816L189.818%20166.666C185.825%20159.468%20184.763%20151.007%20186.852%20143.044C188.941%20135.082%20194.02%20128.232%20201.032%20123.921ZM558.715%20642.82C506.586%20678.213%20445.009%20697.092%20382%20697C208.025%20697%2067%20555.974%2067%20382C67%20322.969%2083.254%20267.718%20111.478%20220.5C112.696%20218.456%20114.432%20216.77%20116.509%20215.61C118.587%20214.45%20120.933%20213.858%20123.312%20213.893C125.691%20213.928%20128.019%20214.589%20130.061%20215.81C132.103%20217.03%20133.788%20218.768%20134.945%20220.846L211.49%20358.595C212.824%20360.993%20213.508%20363.697%20213.474%20366.44C213.441%20369.183%20212.692%20371.87%20211.302%20374.235C209.911%20376.599%20207.926%20378.56%20205.545%20379.922C203.164%20381.284%20200.468%20382%20197.725%20382H130C129.999%20428.603%20142.921%20474.294%20167.33%20513.993C191.739%20553.693%20226.679%20585.846%20268.265%20606.879C309.852%20627.913%20356.457%20637.002%20402.9%20633.137C449.342%20629.272%20493.803%20612.604%20531.341%20584.986C534.645%20582.52%20538.43%20580.775%20542.451%20579.865C546.472%20578.954%20550.64%20578.898%20554.684%20579.7C558.728%20580.501%20562.558%20582.143%20565.928%20584.519C569.297%20586.894%20572.131%20589.951%20574.244%20593.491C578.821%20601.138%20580.227%20610.272%20578.161%20618.941C576.095%20627.61%20570.721%20635.129%20563.188%20639.89C561.702%20640.833%20560.232%20641.799%20558.778%20642.788L558.715%20642.82Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M201.032%20123.921C202.67%20122.913%20204.182%20121.936%20205.537%20121.023C257.614%2085.7368%20319.094%2066.9154%20382%2067.0003C555.974%2067.0003%20697%20208.026%20697%20382C697.086%20440.93%20680.568%20498.691%20649.34%20548.666C648.405%20550.159%20647.097%20551.383%20645.545%20552.216C643.993%20553.05%20642.25%20553.465%20640.489%20553.42C638.728%20553.376%20637.009%20552.873%20635.501%20551.962C633.993%20551.051%20632.748%20549.763%20631.889%20548.225L552.509%20405.404C551.176%20403.007%20550.492%20400.303%20550.525%20397.56C550.558%20394.817%20551.307%20392.13%20552.698%20389.765C554.089%20387.401%20556.073%20385.44%20558.454%20384.078C560.836%20382.716%20563.531%20382%20566.275%20382H634C634.01%20335.412%20621.106%20289.734%20596.72%20250.038C572.334%20210.343%20537.422%20178.184%20495.861%20157.135C454.3%20136.085%20407.718%20126.968%20361.288%20130.797C314.858%20134.626%20270.398%20151.251%20232.847%20178.825C229.627%20181.188%20225.94%20182.839%20222.032%20183.668C218.124%20184.497%20214.085%20184.485%20210.182%20183.633C206.279%20182.781%20202.602%20181.108%20199.395%20178.726C196.189%20176.344%20193.525%20173.307%20191.582%20169.816L189.818%20166.666C185.825%20159.468%20184.763%20151.007%20186.852%20143.044C188.941%20135.082%20194.02%20128.232%20201.032%20123.921ZM558.715%20642.82C506.586%20678.213%20445.009%20697.092%20382%20697C208.025%20697%2067%20555.974%2067%20382C67%20322.969%2083.254%20267.718%20111.478%20220.5C112.696%20218.456%20114.432%20216.77%20116.509%20215.61C118.587%20214.45%20120.933%20213.858%20123.312%20213.893C125.691%20213.928%20128.019%20214.589%20130.061%20215.81C132.103%20217.03%20133.788%20218.768%20134.945%20220.846L211.49%20358.595C212.824%20360.993%20213.508%20363.697%20213.474%20366.44C213.441%20369.183%20212.692%20371.87%20211.302%20374.235C209.911%20376.599%20207.926%20378.56%20205.545%20379.922C203.164%20381.284%20200.468%20382%20197.725%20382H130C129.999%20428.603%20142.921%20474.294%20167.33%20513.993C191.739%20553.693%20226.679%20585.846%20268.265%20606.879C309.852%20627.913%20356.457%20637.002%20402.9%20633.137C449.342%20629.272%20493.803%20612.604%20531.341%20584.986C534.645%20582.52%20538.43%20580.775%20542.451%20579.865C546.472%20578.954%20550.64%20578.898%20554.684%20579.7C558.728%20580.501%20562.558%20582.143%20565.928%20584.519C569.297%20586.894%20572.131%20589.951%20574.244%20593.491C578.821%20601.138%20580.227%20610.272%20578.161%20618.941C576.095%20627.61%20570.721%20635.129%20563.188%20639.89C561.702%20640.833%20560.232%20641.799%20558.778%20642.788L558.715%20642.82Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat}header #indicators span[data-indicator][data-indicator=poll-status][data-style=inactive]:before{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M571.418%20595.528C580.7%20609.514%20576.962%20628.288%20563.018%20637.696C533.911%20657.268%20510.181%20670.456%20491.868%20677.302C456.716%20690.382%20419.502%20697.054%20381.995%20697C354.694%20697%20328.234%20693.514%20302.949%20687.004L335.962%20629.8C396.485%20641.014%20459.864%20629.8%20513.289%20597.124L530.089%20586.708C536.769%20582.478%20544.84%20581.032%20552.573%20582.681C560.306%20584.329%20567.086%20588.941%20571.46%20595.528H571.418ZM509.803%2094.006C529.123%20102.574%20547.477%20113.074%20564.53%20125.212L250.869%20668.482C231.733%20659.714%20213.526%20649.048%20196.52%20636.646L509.761%2094.006H509.803ZM381.995%2067C409.295%2067%20435.839%2070.486%20461.082%2076.996L427.985%20134.284L422.063%20133.234C371.12%20125.021%20318.883%20132.645%20272.412%20155.075C225.941%20177.505%20187.476%20213.659%20162.214%20258.653C136.953%20303.647%20126.112%20355.311%20131.158%20406.664C136.204%20458.017%20156.893%20506.583%20190.43%20545.8L157.459%20602.92C113.918%20558.666%2084.4251%20502.532%2072.6843%20441.57C60.9436%20380.609%2067.4788%20317.537%2091.469%20260.277C115.459%20203.018%20155.835%20154.123%20207.524%20119.736C259.213%2085.349%20319.912%2067.0021%20381.995%2067ZM606.572%20161.08C664.623%20219.944%20697.117%20299.327%20697%20382C697.058%20427.158%20687.374%20471.797%20668.607%20512.872C664.536%20521.456%20660.05%20529.838%20655.167%20537.988C653.67%20540.539%20651.526%20542.649%20648.952%20544.106C646.378%20545.563%20643.465%20546.315%20640.508%20546.285C637.55%20546.255%20634.653%20545.445%20632.109%20543.936C629.564%20542.428%20627.464%20540.274%20626.019%20537.694L609.932%20508.714L553.357%20406.948C551.939%20404.39%20551.212%20401.505%20551.251%20398.581C551.289%20395.656%20552.09%20392.791%20553.575%20390.271C555.06%20387.751%20557.177%20385.662%20559.717%20384.212C562.258%20382.761%20565.132%20381.998%20568.058%20382H633.999C634.02%20321.959%20612.568%20263.891%20573.518%20218.284L606.572%20161.122V161.08Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20width='800'%20height='800'%20viewBox='0%200%20800%20800'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M571.418%20595.528C580.7%20609.514%20576.962%20628.288%20563.018%20637.696C533.911%20657.268%20510.181%20670.456%20491.868%20677.302C456.716%20690.382%20419.502%20697.054%20381.995%20697C354.694%20697%20328.234%20693.514%20302.949%20687.004L335.962%20629.8C396.485%20641.014%20459.864%20629.8%20513.289%20597.124L530.089%20586.708C536.769%20582.478%20544.84%20581.032%20552.573%20582.681C560.306%20584.329%20567.086%20588.941%20571.46%20595.528H571.418ZM509.803%2094.006C529.123%20102.574%20547.477%20113.074%20564.53%20125.212L250.869%20668.482C231.733%20659.714%20213.526%20649.048%20196.52%20636.646L509.761%2094.006H509.803ZM381.995%2067C409.295%2067%20435.839%2070.486%20461.082%2076.996L427.985%20134.284L422.063%20133.234C371.12%20125.021%20318.883%20132.645%20272.412%20155.075C225.941%20177.505%20187.476%20213.659%20162.214%20258.653C136.953%20303.647%20126.112%20355.311%20131.158%20406.664C136.204%20458.017%20156.893%20506.583%20190.43%20545.8L157.459%20602.92C113.918%20558.666%2084.4251%20502.532%2072.6843%20441.57C60.9436%20380.609%2067.4788%20317.537%2091.469%20260.277C115.459%20203.018%20155.835%20154.123%20207.524%20119.736C259.213%2085.349%20319.912%2067.0021%20381.995%2067ZM606.572%20161.08C664.623%20219.944%20697.117%20299.327%20697%20382C697.058%20427.158%20687.374%20471.797%20668.607%20512.872C664.536%20521.456%20660.05%20529.838%20655.167%20537.988C653.67%20540.539%20651.526%20542.649%20648.952%20544.106C646.378%20545.563%20643.465%20546.315%20640.508%20546.285C637.55%20546.255%20634.653%20545.445%20632.109%20543.936C629.564%20542.428%20627.464%20540.274%20626.019%20537.694L609.932%20508.714L553.357%20406.948C551.939%20404.39%20551.212%20401.505%20551.251%20398.581C551.289%20395.656%20552.09%20392.791%20553.575%20390.271C555.06%20387.751%20557.177%20385.662%20559.717%20384.212C562.258%20382.761%20565.132%20381.998%20568.058%20382H633.999C634.02%20321.959%20612.568%20263.891%20573.518%20218.284L606.572%20161.122V161.08Z'%20fill='%230F162B'/%3e%3c/svg%3e") 50%/cover no-repeat}header #indicators span[data-indicator][data-indicator=uci-changes]{position:relative}header #indicators span[data-indicator][data-indicator=uci-changes]:before{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1758476543825'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='17352'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M439.594667%2085.333333h144.810666a70.4%2070.4%200%200%201%2070.101334%2060.970667l5.802666%2044.8a17.066667%2017.066667%200%200%200%208.192%2012.458667l38.058667%2022.528a17.066667%2017.066667%200%200%200%2015.146667%201.109333l46.848-19.2a71.082667%2071.082667%200%200%201%2088.021333%2029.226667l72.405333%20122.069333c18.218667%2030.72%2010.069333%2069.973333-18.688%2091.221333l-45.184%2033.408a17.066667%2017.066667%200%200%200-6.912%2013.696v28.757334a17.066667%2017.066667%200%200%200%206.912%2013.696l45.226667%2033.408c28.714667%2021.205333%2036.864%2060.458667%2018.645333%2091.221333l-72.405333%20122.026667a71.082667%2071.082667%200%200%201-88.021333%2029.226666l-46.848-19.2a17.066667%2017.066667%200%200%200-15.146667%201.152l-38.058667%2022.528a17.066667%2017.066667%200%200%200-8.192%2012.501334l-5.802666%2044.8A70.4%2070.4%200%200%201%20584.405333%20938.666667h-144.810666a70.4%2070.4%200%200%201-70.101334-60.970667l-5.802666-44.8a17.066667%2017.066667%200%200%200-8.192-12.458667l-38.058667-22.528a17.066667%2017.066667%200%200%200-15.146667-1.109333l-46.848%2019.2a71.082667%2071.082667%200%200%201-88.021333-29.226667l-72.405333-122.069333a69.290667%2069.290667%200%200%201%2018.688-91.221333l45.184-33.408a17.066667%2017.066667%200%200%200%206.912-13.696v-28.757334a17.066667%2017.066667%200%200%200-6.912-13.696l-45.226667-33.408a69.290667%2069.290667%200%200%201-18.645333-91.221333l72.405333-122.026667a71.082667%2071.082667%200%200%201%2088.021333-29.226666l46.848%2019.2a17.066667%2017.066667%200%200%200%2015.146667-1.152l38.058667-22.528a17.066667%2017.066667%200%200%200%208.192-12.501334l5.802666-44.8A70.4%2070.4%200%200%201%20439.594667%2085.333333z%20m1.024%20130.688a75.818667%2075.818667%200%200%201-36.821334%2055.466667l-51.712%2030.464a76.970667%2076.970667%200%200%201-67.925333%204.906667l-35.328-14.336a17.066667%2017.066667%200%200%200-21.077333%207.04L178.645333%20381.738667a17.066667%2017.066667%200%200%200%204.565334%2022.528l33.066666%2024.277333c19.541333%2014.293333%2031.018667%2036.906667%2031.018667%2061.013333v44.885334c0%2024.106667-11.52%2046.72-31.018667%2061.013333l-33.066666%2024.277333a17.066667%2017.066667%200%200%200-4.565334%2022.528l49.066667%2082.176a17.066667%2017.066667%200%200%200%2021.12%207.04l35.328-14.336a76.970667%2076.970667%200%200%201%2067.925333%204.906667l51.712%2030.421333c20.224%2011.946667%2033.792%2032.384%2036.821334%2055.509334l4.010666%2030.506666a17.066667%2017.066667%200%200%200%2016.896%2014.848h100.949334a17.066667%2017.066667%200%200%200%2016.896-14.848l4.010666-30.506666c2.986667-23.125333%2016.597333-43.605333%2036.821334-55.466667l51.712-30.464a76.970667%2076.970667%200%200%201%2067.925333-4.906667l35.328%2014.336a17.066667%2017.066667%200%200%200%2021.077333-7.04l49.109334-82.176a17.066667%2017.066667%200%200%200-4.565334-22.528l-33.066666-24.277333a75.648%2075.648%200%200%201-31.018667-61.013333v-44.885334c0-24.106667%2011.52-46.72%2031.018667-61.013333l33.066666-24.277333a17.066667%2017.066667%200%200%200%204.565334-22.528l-49.066667-82.176a17.066667%2017.066667%200%200%200-21.12-7.04l-35.328%2014.336c-22.186667%209.045333-47.317333%207.210667-67.925333-4.906667l-51.712-30.421333a75.818667%2075.818667%200%200%201-36.821334-55.509334l-4.010666-30.506666A17.066667%2017.066667%200%200%200%20562.474667%20170.666667h-100.949334a17.066667%2017.066667%200%200%200-16.896%2014.848l-4.010666%2030.506666zM682.666667%20512a42.666667%2042.666667%200%200%201-85.333334%200%2085.333333%2085.333333%200%201%200-85.333333%2085.333333%2042.666667%2042.666667%200%200%201%200%2085.333334%20170.666667%20170.666667%200%201%201%20170.666667-170.666667z'%20fill='%230f162b'%20p-id='17353'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1758476543825'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='17352'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M439.594667%2085.333333h144.810666a70.4%2070.4%200%200%201%2070.101334%2060.970667l5.802666%2044.8a17.066667%2017.066667%200%200%200%208.192%2012.458667l38.058667%2022.528a17.066667%2017.066667%200%200%200%2015.146667%201.109333l46.848-19.2a71.082667%2071.082667%200%200%201%2088.021333%2029.226667l72.405333%20122.069333c18.218667%2030.72%2010.069333%2069.973333-18.688%2091.221333l-45.184%2033.408a17.066667%2017.066667%200%200%200-6.912%2013.696v28.757334a17.066667%2017.066667%200%200%200%206.912%2013.696l45.226667%2033.408c28.714667%2021.205333%2036.864%2060.458667%2018.645333%2091.221333l-72.405333%20122.026667a71.082667%2071.082667%200%200%201-88.021333%2029.226666l-46.848-19.2a17.066667%2017.066667%200%200%200-15.146667%201.152l-38.058667%2022.528a17.066667%2017.066667%200%200%200-8.192%2012.501334l-5.802666%2044.8A70.4%2070.4%200%200%201%20584.405333%20938.666667h-144.810666a70.4%2070.4%200%200%201-70.101334-60.970667l-5.802666-44.8a17.066667%2017.066667%200%200%200-8.192-12.458667l-38.058667-22.528a17.066667%2017.066667%200%200%200-15.146667-1.109333l-46.848%2019.2a71.082667%2071.082667%200%200%201-88.021333-29.226667l-72.405333-122.069333a69.290667%2069.290667%200%200%201%2018.688-91.221333l45.184-33.408a17.066667%2017.066667%200%200%200%206.912-13.696v-28.757334a17.066667%2017.066667%200%200%200-6.912-13.696l-45.226667-33.408a69.290667%2069.290667%200%200%201-18.645333-91.221333l72.405333-122.026667a71.082667%2071.082667%200%200%201%2088.021333-29.226666l46.848%2019.2a17.066667%2017.066667%200%200%200%2015.146667-1.152l38.058667-22.528a17.066667%2017.066667%200%200%200%208.192-12.501334l5.802666-44.8A70.4%2070.4%200%200%201%20439.594667%2085.333333z%20m1.024%20130.688a75.818667%2075.818667%200%200%201-36.821334%2055.466667l-51.712%2030.464a76.970667%2076.970667%200%200%201-67.925333%204.906667l-35.328-14.336a17.066667%2017.066667%200%200%200-21.077333%207.04L178.645333%20381.738667a17.066667%2017.066667%200%200%200%204.565334%2022.528l33.066666%2024.277333c19.541333%2014.293333%2031.018667%2036.906667%2031.018667%2061.013333v44.885334c0%2024.106667-11.52%2046.72-31.018667%2061.013333l-33.066666%2024.277333a17.066667%2017.066667%200%200%200-4.565334%2022.528l49.066667%2082.176a17.066667%2017.066667%200%200%200%2021.12%207.04l35.328-14.336a76.970667%2076.970667%200%200%201%2067.925333%204.906667l51.712%2030.421333c20.224%2011.946667%2033.792%2032.384%2036.821334%2055.509334l4.010666%2030.506666a17.066667%2017.066667%200%200%200%2016.896%2014.848h100.949334a17.066667%2017.066667%200%200%200%2016.896-14.848l4.010666-30.506666c2.986667-23.125333%2016.597333-43.605333%2036.821334-55.466667l51.712-30.464a76.970667%2076.970667%200%200%201%2067.925333-4.906667l35.328%2014.336a17.066667%2017.066667%200%200%200%2021.077333-7.04l49.109334-82.176a17.066667%2017.066667%200%200%200-4.565334-22.528l-33.066666-24.277333a75.648%2075.648%200%200%201-31.018667-61.013333v-44.885334c0-24.106667%2011.52-46.72%2031.018667-61.013333l33.066666-24.277333a17.066667%2017.066667%200%200%200%204.565334-22.528l-49.066667-82.176a17.066667%2017.066667%200%200%200-21.12-7.04l-35.328%2014.336c-22.186667%209.045333-47.317333%207.210667-67.925333-4.906667l-51.712-30.421333a75.818667%2075.818667%200%200%201-36.821334-55.509334l-4.010666-30.506666A17.066667%2017.066667%200%200%200%20562.474667%20170.666667h-100.949334a17.066667%2017.066667%200%200%200-16.896%2014.848l-4.010666%2030.506666zM682.666667%20512a42.666667%2042.666667%200%200%201-85.333334%200%2085.333333%2085.333333%200%201%200-85.333333%2085.333333%2042.666667%2042.666667%200%200%201%200%2085.333334%20170.666667%20170.666667%200%201%201%20170.666667-170.666667z'%20fill='%230f162b'%20p-id='17353'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat}header #indicators span[data-indicator][data-indicator=uci-changes][data-count]:not([data-count="0"]):after{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*-.5);content:var(--tw-content);right:calc(var(--spacing)*-.5);content:var(--tw-content);z-index:10;content:var(--tw-content);content:var(--tw-content);min-height:calc(var(--spacing)*3);content:var(--tw-content);min-width:calc(var(--spacing)*3);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);background-color:var(--accent);content:var(--tw-content);padding-inline:calc(var(--spacing)*.5);content:var(--tw-content);content:var(--tw-content);--tw-leading:1;content:var(--tw-content);--tw-font-weight:var(--font-weight-bold);font-size:8px;line-height:1;font-weight:var(--font-weight-bold);content:var(--tw-content);color:var(--accent-foreground);content:var(--tw-content);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-content:attr(data-count);content:var(--tw-content);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex;position:absolute}#maincontent{width:95.8333%;min-height:calc(100vh - 4rem);max-width:var(--container-max-width);padding-inline:calc(var(--spacing)*4);margin-inline:auto}@media not all and (min-width:48rem){#maincontent{width:100%;padding-inline:calc(var(--spacing)*3)}}#maincontent #view{margin-inline:calc(var(--spacing)*0);width:100%;animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none);padding:calc(var(--spacing)*0);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-enter-opacity:0;--tw-animation-fill-mode:both;--tw-enter-translate-y:calc(2*var(--spacing)*-1);transition-duration:.3s;animation-fill-mode:both}#maincontent #view:empty{display:none}@media (min-width:48rem){#maincontent #view{border-radius:calc(var(--radius-base)*4);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#maincontent #view{border-color:color-mix(in oklab,var(--border)30%,transparent)}}#maincontent #view{background-color:var(--page-bg);padding:calc(var(--spacing)*6);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){#maincontent #view:hover{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#maincontent #view:hover{border-color:color-mix(in oklab,var(--border)50%,transparent)}}#maincontent #view:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}}#maincontent #view .cbi-title-section{margin-bottom:calc(var(--spacing)*6);--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}@media not all and (min-width:48rem){#maincontent #view .cbi-title-section{margin-inline:calc(var(--spacing)*2);margin-bottom:calc(var(--spacing)*3)}}#maincontent #view .controls{margin:calc(var(--spacing)*2);align-items:center;gap:calc(var(--spacing)*3);flex-wrap:wrap;display:flex}@media not all and (min-width:48rem){#maincontent #view .controls{margin:calc(var(--spacing)*1);gap:calc(var(--spacing)*2)}}#maincontent #view .controls label input[type=radio],#maincontent #view .controls label input[type=checkbox]{margin-right:calc(var(--spacing)*1);vertical-align:middle}#maincontent #view #content_syslog{margin:calc(var(--spacing)*2)}@media not all and (min-width:48rem){#maincontent #view #content_syslog{margin:calc(var(--spacing)*1)}}#maincontent #view #content_syslog>div{align-items:baseline;column-gap:calc(var(--spacing)*2);flex-wrap:wrap;display:flex}@media not all and (min-width:48rem){#maincontent #view #content_syslog>div{column-gap:calc(var(--spacing)*1.5)}#maincontent #view #content_syslog>div>.cbi-input-select,#maincontent #view #content_syslog>div>.cbi-input-text{min-width:calc(var(--spacing)*45)}}#maincontent #view div[style]>svg:where([data-darkmode=true],[data-darkmode=true] *){background-color:var(--page-bg)!important}#maincontent #view div[style]>svg line[style]:where([data-darkmode=true],[data-darkmode=true] *){stroke:var(--foreground)!important}#maincontent .cbi-map-descr,#maincontent .cbi-section-descr{margin-bottom:calc(var(--spacing)*6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--muted-foreground)}@media not all and (min-width:48rem){#maincontent .cbi-map-descr,#maincontent .cbi-section-descr{margin-inline:calc(var(--spacing)*2);margin-bottom:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}#maincontent .cbi-page-actions{margin-top:calc(var(--spacing)*6);justify-content:flex-end;align-items:center;gap:calc(var(--spacing)*3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border);flex-wrap:wrap;display:flex}@supports (color:color-mix(in lab, red, red)){#maincontent .cbi-page-actions{border-color:color-mix(in oklab,var(--border)50%,transparent)}}#maincontent .cbi-page-actions{padding-top:calc(var(--spacing)*4)}@media not all and (min-width:48rem){#maincontent .cbi-page-actions{margin-inline:calc(var(--spacing)*2);margin-top:calc(var(--spacing)*4);gap:calc(var(--spacing)*2);padding-top:calc(var(--spacing)*3)}}#maincontent .zone-forwards{align-items:flex-start;gap:calc(var(--spacing)*3);--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);display:flex}#maincontent .zone-forwards>span{margin-top:calc(var(--spacing)*1.5);color:var(--muted-foreground)}#maincontent .zone-forwards .zone-dest{align-items:flex-start;gap:calc(var(--spacing)*2);flex-direction:column;display:flex}#maincontent #syslog,#maincontent #log_textarea{width:100%;font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}#maincontent #syslog{border-radius:calc(var(--radius-base)*3);border-style:var(--tw-border-style);background-color:var(--terminal-bg);padding:calc(var(--spacing)*6);color:var(--terminal-foreground);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-width:1px}@media not all and (min-width:48rem){#maincontent #syslog{padding:calc(var(--spacing)*3)}}footer{margin-top:calc(var(--spacing)*2);min-height:calc(var(--spacing)*16);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--secondary-foreground);flex-wrap:wrap;justify-content:space-between;align-items:center;display:flex}footer a{color:var(--primary)}footer span{text-align:center;display:inline-block}footer .breadcrumb{align-items:center;gap:calc(var(--spacing)*1);background-color:var(--page-bg);border-radius:3.40282e38px;display:flex}@supports (color:color-mix(in lab, red, red)){footer .breadcrumb{background-color:color-mix(in oklab,var(--page-bg)60%,transparent)}}footer .breadcrumb{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}@media (hover:hover){footer .breadcrumb:hover{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){footer .breadcrumb:hover{background-color:color-mix(in oklab,var(--page-bg)80%,transparent)}}footer .breadcrumb:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}footer .breadcrumb li{list-style-type:none}footer .breadcrumb li.active a{color:var(--default-foreground)}.control-group{margin-top:calc(var(--spacing)*1)}.control-group,.cbi-page-actions>div,.cbi-section-actions>div{align-items:center;gap:calc(var(--spacing)*2);flex-flow:row;display:flex}@media not all and (min-width:48rem){.control-group,.cbi-page-actions>div,.cbi-section-actions>div{gap:calc(var(--spacing)*1);flex-wrap:wrap}}.theme-switcher{align-items:center;gap:calc(var(--spacing)*0);background-color:var(--page-bg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;border-radius:3.40282e38px;transition-duration:.3s;display:inline-flex;position:relative}@media not all and (min-width:48rem){.theme-switcher{padding-block:calc(var(--spacing)*.5)}}.theme-switcher{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.theme-switcher{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.theme-switcher:before{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*1);content:var(--tw-content);left:calc(var(--spacing)*1);content:var(--tw-content);z-index:0;content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);background-color:var(--border);border-radius:3.40282e38px;width:calc(33.333% - .5rem);height:calc(100% - .5rem);position:absolute}@supports (color:color-mix(in lab, red, red)){.theme-switcher:before{background-color:color-mix(in oklab,var(--border)50%,transparent)}}.theme-switcher:before{content:var(--tw-content);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);content:var(--tw-content);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);content:var(--tw-content);--tw-ring-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.theme-switcher:before{--tw-ring-color:color-mix(in oklab,var(--border)60%,transparent)}}.theme-switcher:before{content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));content:var(--tw-content);--tw-duration:.3s;transition-duration:.3s}@media not all and (min-width:48rem){footer .theme-switcher{display:none}}.theme-switcher .theme-option{z-index:10;cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;border-radius:3.40282e38px;transition-duration:.3s;display:flex;position:relative}@media (hover:hover){.theme-switcher .theme-option:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.theme-switcher .theme-option:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.theme-switcher .theme-option input[type=radio]{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.theme-switcher .theme-option .theme-icon{color:var(--muted-foreground);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;justify-content:center;align-items:center;transition-duration:.3s;display:flex}.theme-switcher .theme-option .theme-icon svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}.theme-switcher .theme-option .theme-label{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--muted-foreground);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s;display:none}.theme-switcher .theme-option.active .theme-icon{color:var(--foreground)}.theme-switcher .theme-option.active .theme-icon svg{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.theme-switcher .theme-option.active .theme-label,.theme-switcher .theme-option:hover:not(.active) .theme-icon,.theme-switcher .theme-option:hover:not(.active) .theme-label{color:var(--foreground)}.theme-switcher:has(.theme-option[data-theme=device].active):before{content:var(--tw-content);--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.theme-switcher:has(.theme-option[data-theme=light].active):before{content:var(--tw-content);--tw-translate-x:calc(100% + .5rem);translate:var(--tw-translate-x)var(--tw-translate-y)}.theme-switcher:has(.theme-option[data-theme=dark].active):before{content:var(--tw-content);--tw-translate-x:calc(200% + 1rem);translate:var(--tw-translate-x)var(--tw-translate-y)}.floating-toolbar{right:calc(var(--spacing)*4);bottom:calc(var(--spacing)*4);z-index:40;flex-direction:column;align-items:center;display:flex;position:fixed}@media not all and (min-width:48rem){.floating-toolbar{right:calc(var(--spacing)*3);bottom:calc(var(--spacing)*3)}}.floating-toolbar .toolbar-list{visibility:visible;margin-bottom:calc(var(--spacing)*2);transform-origin:bottom;opacity:1;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.4s;--tw-ease:var(--ease-in-out);transition-duration:.4s;transition-timing-function:var(--ease-in-out);grid-template-rows:1fr;display:grid}@media not all and (min-width:48rem){.floating-toolbar .toolbar-list{margin-bottom:calc(var(--spacing)*1.5)}}.floating-toolbar .toolbar-list .toolbar-list-inner{min-height:calc(var(--spacing)*0);align-items:center;gap:calc(var(--spacing)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);border-radius:3.40282e38px;flex-direction:column;display:flex;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.floating-toolbar .toolbar-list .toolbar-list-inner{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.floating-toolbar .toolbar-list .toolbar-list-inner{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){.floating-toolbar .toolbar-list .toolbar-list-inner{background-color:color-mix(in oklab,var(--page-bg)90%,transparent)}}.floating-toolbar .toolbar-list .toolbar-list-inner{padding:calc(var(--spacing)*1);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media not all and (min-width:48rem){.floating-toolbar .toolbar-list .toolbar-list-inner{gap:calc(var(--spacing)*1.5);padding:calc(var(--spacing)*.5)}}.floating-toolbar .toolbar-btn{height:calc(var(--spacing)*10);width:calc(var(--spacing)*10);cursor:pointer;padding:calc(var(--spacing)*2);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-in-out);transition-duration:.3s;transition-timing-function:var(--ease-in-out);border-radius:3.40282e38px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.floating-toolbar .toolbar-btn:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}@media not all and (min-width:48rem){.floating-toolbar .toolbar-btn{height:calc(var(--spacing)*9);width:calc(var(--spacing)*9);padding:calc(var(--spacing)*1.5)}}.floating-toolbar .toolbar-btn .icon{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-in-out);transition-duration:.3s;transition-timing-function:var(--ease-in-out);flex-shrink:0}@media not all and (min-width:48rem){.floating-toolbar .toolbar-btn .icon{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4)}}.floating-toolbar .toolbar-btn:not(.toggle){text-decoration-line:none}@media (hover:hover){.floating-toolbar .toolbar-btn:not(.toggle):hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y);background-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.floating-toolbar .toolbar-btn:not(.toggle):hover{background-color:color-mix(in oklab,var(--border)40%,transparent)}}}.floating-toolbar .toolbar-btn:not(.toggle) .icon{object-fit:contain}.floating-toolbar .toolbar-btn:not(.toggle) .icon:where([data-darkmode=true],[data-darkmode=true] *){--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.floating-toolbar .toolbar-btn.toggle{border-style:var(--tw-border-style);background-color:var(--page-bg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-width:0}.floating-toolbar .toolbar-btn.toggle .icon{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.4s;--tw-ease:var(--ease-out);transition-duration:.4s;transition-timing-function:var(--ease-out)}.floating-toolbar.collapsed .toolbar-list{pointer-events:none;visibility:hidden;margin-bottom:calc(var(--spacing)*0);--tw-translate-y:calc(var(--spacing)*2);translate:var(--tw-translate-x)var(--tw-translate-y);--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x)var(--tw-scale-y);opacity:0;grid-template-rows:0fr}.floating-toolbar.collapsed .toolbar-btn:not(.toggle){--tw-scale-x:50%;--tw-scale-y:50%;--tw-scale-z:50%;scale:var(--tw-scale-x)var(--tw-scale-y);opacity:0}.floating-toolbar.collapsed .toolbar-btn.toggle .icon{rotate:45deg}button,input[type=button],input[type=submit],input[type=reset],.btn,.cbi-button{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing)*1.5);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-width:1px;display:inline-flex}@media not all and (min-width:48rem){button,input[type=button],input[type=submit],input[type=reset],.btn,.cbi-button{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}}:is(button,input[type=button],input[type=submit],input[type=reset],.btn,.cbi-button)[disabled]{cursor:not-allowed;opacity:.4}:is(button,input[type=button],input[type=submit],input[type=reset],.btn,.cbi-button)[disabled]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}.td :is(button,input[type=button],input[type=submit],input[type=reset],.btn,.cbi-button){white-space:nowrap;flex:none;width:auto;display:inline-flex}@media not all and (min-width:48rem){.td :is(button,input[type=button],input[type=submit],input[type=reset],.btn,.cbi-button){min-height:calc(var(--spacing)*9);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}.btn,.cbi-button,.cbi-button-default,.cbi-button-download,.cbi-button-find,.cbi-button-link,.cbi-button-up,.cbi-button-down{border-color:var(--secondary);background-color:var(--secondary);color:var(--secondary-foreground)}@media (hover:hover){:is(.btn,.cbi-button,.cbi-button-default,.cbi-button-download,.cbi-button-find,.cbi-button-link,.cbi-button-up,.cbi-button-down):hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){:is(.btn,.cbi-button,.cbi-button-default,.cbi-button-download,.cbi-button-find,.cbi-button-link,.cbi-button-up,.cbi-button-down):hover{background-color:color-mix(in oklab,var(--secondary)90%,transparent)}}}.drag-handle{border-color:var(--muted);background-color:var(--muted);color:var(--muted-foreground)}@media (hover:hover){.drag-handle:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.drag-handle:hover{background-color:color-mix(in oklab,var(--muted)90%,transparent)}}}.btn.primary,.cbi-button-action,.cbi-button-apply,.cbi-button-reload,.cbi-button-add,.cbi-button-edit{border-color:var(--primary);background-color:var(--primary);color:var(--primary-foreground)}@media (hover:hover){:is(.btn.primary,.cbi-button-action,.cbi-button-apply,.cbi-button-reload,.cbi-button-add,.cbi-button-edit):hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){:is(.btn.primary,.cbi-button-action,.cbi-button-apply,.cbi-button-reload,.cbi-button-add,.cbi-button-edit):hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}}}.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{border-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{border-color:color-mix(in oklab,var(--primary)25%,transparent)}}.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{background-color:color-mix(in oklab,var(--primary)25%,transparent)}}.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save{color:var(--primary)}@media (hover:hover){:is(.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save):hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){:is(.cbi-button-positive,.cbi-button-fieldadd,.cbi-button-save):hover{background-color:color-mix(in oklab,var(--primary)30%,transparent)}}}.cbi-button-negative,.cbi-section-remove .cbi-button,.cbi-button-reset,.cbi-button-remove{border-color:var(--destructive);background-color:var(--destructive);color:var(--destructive-foreground)}@media (hover:hover){:is(.cbi-button-negative,.cbi-section-remove .cbi-button,.cbi-button-reset,.cbi-button-remove):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){:is(.cbi-button-negative,.cbi-section-remove .cbi-button,.cbi-button-reset,.cbi-button-remove):hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}}}input:disabled{cursor:not-allowed;opacity:.4}input:disabled:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}input[type=text],input[type=password],.cbi-input-text,.cbi-input{border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);position:relative}@supports (color:color-mix(in lab, red, red)){input[type=text],input[type=password],.cbi-input-text,.cbi-input{border-color:color-mix(in oklab,var(--border)70%,transparent)}}input[type=text],input[type=password],.cbi-input-text,.cbi-input{background-color:var(--page-bg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--foreground)}:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input)::placeholder{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input)::placeholder{color:color-mix(in oklab,var(--muted-foreground)70%,transparent)}}input[type=text],input[type=password],.cbi-input-text,.cbi-input{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input):focus{border-color:var(--primary);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input):focus{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}:is(input[type=text],input[type=password],.cbi-input-text,.cbi-input):focus{--tw-outline-style:none;outline-style:none}.table.cbi-section-table :is(input[type=text],input[type=password],.cbi-input-text,.cbi-input){width:100%}.cbi-input-text{min-width:calc(var(--spacing)*20)}@media not all and (min-width:48rem){.cbi-input-text{min-width:calc(var(--spacing)*14)}}#localtime{min-width:calc(var(--spacing)*70)}input[type=radio],input[type=checkbox]{margin-right:calc(var(--spacing)*3);height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);cursor:pointer;appearance:none;display:inline-block;position:relative}:is(input[type=radio],input[type=checkbox]):before{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*0);content:var(--tw-content);left:calc(var(--spacing)*0);content:var(--tw-content);height:calc(var(--spacing)*4);content:var(--tw-content);width:calc(var(--spacing)*4);content:var(--tw-content);border-style:var(--tw-border-style);content:var(--tw-content);border-width:1px;border-color:var(--border);position:absolute}@supports (color:color-mix(in lab, red, red)){:is(input[type=radio],input[type=checkbox]):before{border-color:color-mix(in oklab,var(--border)70%,transparent)}}:is(input[type=radio],input[type=checkbox]):before{content:var(--tw-content);background-color:var(--page-bg)}:is(input[type=radio],input[type=checkbox]):after{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*.5);content:var(--tw-content);left:calc(var(--spacing)*.5);content:var(--tw-content);height:calc(var(--spacing)*3);content:var(--tw-content);width:calc(var(--spacing)*3);content:var(--tw-content);background-color:var(--input-checked);content:var(--tw-content);opacity:0;content:var(--tw-content);transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));content:var(--tw-content);--tw-duration:.2s;transition-duration:.2s;position:absolute}:is(input[type=radio],input[type=checkbox]):checked:before{content:var(--tw-content);border-color:var(--primary);content:var(--tw-content);background-color:var(--primary)}:is(input[type=radio],input[type=checkbox]):checked:after{content:var(--tw-content);opacity:1}@media (hover:hover){:is(input[type=radio],input[type=checkbox]):hover:before{content:var(--tw-content);border-color:var(--border)}}:is(input[type=radio],input[type=checkbox]):focus:before{content:var(--tw-content);border-color:var(--primary);content:var(--tw-content);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);content:var(--tw-content);--tw-ring-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){:is(input[type=radio],input[type=checkbox]):focus:before{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}:is(input[type=radio],input[type=checkbox]):focus:before{content:var(--tw-content);--tw-outline-style:none;outline-style:none}:is(input[type=radio],input[type=checkbox]):disabled{cursor:not-allowed}input[type=radio]:before{content:var(--tw-content);border-radius:3.40282e38px}input[type=radio]:after{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='currentColor'%3e%3ccircle%20cx='12'%20cy='12'%20r='6'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='currentColor'%3e%3ccircle%20cx='12'%20cy='12'%20r='6'/%3e%3c/svg%3e") 50%/cover no-repeat}input[type=checkbox]:before{content:var(--tw-content);border-radius:calc(var(--radius-base)*.5)}input[type=checkbox]:after{content:var(--tw-content);-webkit-mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1760890864667'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='18898'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M918.8%2083.7c-43.5-30.3-104-19.5-134.6%2024.1l-425.8%20606-127-135.7c-36.4-38.9-98.1-41.1-137-4.8-39%2036.2-41.1%2097.7-4.7%20136.6l206.5%20220.7c4.7%205%209.8%209.4%2015.1%2013.1%200.7%200.5%201.4%201%202.2%201.5%2043.5%2030.3%20104%2019.5%20134.6-24.1l494-703.2c30.7-43.5%2020.1-103.9-23.3-134.2z'%20fill='%230f162b'%20p-id='18899'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1760890864667'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='18898'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M918.8%2083.7c-43.5-30.3-104-19.5-134.6%2024.1l-425.8%20606-127-135.7c-36.4-38.9-98.1-41.1-137-4.8-39%2036.2-41.1%2097.7-4.7%20136.6l206.5%20220.7c4.7%205%209.8%209.4%2015.1%2013.1%200.7%200.5%201.4%201%202.2%201.5%2043.5%2030.3%20104%2019.5%20134.6-24.1l494-703.2c30.7-43.5%2020.1-103.9-23.3-134.2z'%20fill='%230f162b'%20p-id='18899'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat}.cbi-checkbox{align-items:center;display:inline-flex;position:relative}textarea{min-height:calc(var(--spacing)*24);resize:vertical;border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);width:100%}@supports (color:color-mix(in lab, red, red)){textarea{border-color:color-mix(in oklab,var(--border)70%,transparent)}}textarea{background-color:var(--page-bg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--foreground)}textarea::placeholder{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){textarea::placeholder{color:color-mix(in oklab,var(--muted-foreground)70%,transparent)}}textarea{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}textarea:focus{border-color:var(--primary);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){textarea:focus{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}textarea:focus{--tw-outline-style:none;outline-style:none}textarea[disabled]{cursor:not-allowed;opacity:.4}textarea[disabled]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}select{appearance:none;border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){select{border-color:color-mix(in oklab,var(--border)70%,transparent)}}select{background-color:var(--page-bg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);padding-right:calc(var(--spacing)*10);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--foreground);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}select:focus{border-color:var(--primary);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){select:focus{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}select:focus{--tw-outline-style:none;outline-style:none}select{background-image:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M5.22%208.22a.75.75%200%200%201%201.06%200L10%2011.94l3.72-3.72a.75.75%200%201%201%201.06%201.06l-4.25%204.25a.75.75%200%200%201-1.06%200L5.22%209.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e");background-position:right .75rem center;background-repeat:no-repeat;background-size:16px}select:where([data-darkmode=true],[data-darkmode=true] *){background-image:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23ffffff'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M5.22%208.22a.75.75%200%200%201%201.06%200L10%2011.94l3.72-3.72a.75.75%200%201%201%201.06%201.06l-4.25%204.25a.75.75%200%200%201-1.06%200L5.22%209.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e")}select[disabled]{cursor:not-allowed;opacity:.4}select[disabled]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}.cbi-dropdown{min-width:fit-content;max-width:100%;height:fit-content;padding:calc(var(--spacing)*0);white-space:nowrap;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);flex-shrink:0;align-items:center;display:inline-flex}.cbi-dropdown[disabled]{pointer-events:none;opacity:.4}.cbi-dropdown[disabled]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}.cbi-dropdown:not(.btn):not(.cbi-button){border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown:not(.btn):not(.cbi-button){border-color:color-mix(in oklab,var(--border)70%,transparent)}}.cbi-dropdown:not(.btn):not(.cbi-button){background-color:var(--page-bg)}.cbi-dropdown:not(.btn):not(.cbi-button)>ul>li[placeholder]{display:none}.cbi-dropdown>ul{margin:calc(var(--spacing)*0);align-items:center;gap:calc(var(--spacing)*.5);flex-wrap:nowrap;flex:1;list-style-type:none;display:flex;overflow:hidden}@media not all and (min-width:48rem){.cbi-dropdown>ul{flex-wrap:wrap}}.cbi-dropdown>ul>li{margin:calc(var(--spacing)*0);align-items:center;gap:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1.5);list-style-type:none;display:none;overflow:hidden}.cbi-dropdown>ul>li[display]{display:flex!important}.cbi-dropdown>ul>li .hide-open{display:block}.cbi-dropdown>ul>li .hide-close{display:none}.cbi-dropdown>ul>li>form{pointer-events:none;display:none}.cbi-dropdown>ul>li>label{align-items:center;gap:calc(var(--spacing)*2);display:flex}.cbi-dropdown>ul>li img{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5);vertical-align:middle;flex:none}.cbi-dropdown>ul>li span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.cbi-dropdown>ul.dropdown{left:calc(var(--spacing)*0);z-index:60;border-radius:var(--radius-base);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);width:fit-content;min-width:100%;position:absolute;overflow-y:auto}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown>ul.dropdown{border-color:color-mix(in oklab,var(--border)60%,transparent)}}.cbi-dropdown>ul.dropdown{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown>ul.dropdown{background-color:color-mix(in oklab,var(--page-bg)95%,transparent)}}.cbi-dropdown>ul.dropdown{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.cbi-dropdown>ul.dropdown>li{min-height:calc(var(--spacing)*9);cursor:pointer;width:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--muted-foreground)}@media (hover:hover){.cbi-dropdown>ul.dropdown>li:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown>ul.dropdown>li:hover{background-color:color-mix(in oklab,var(--primary)15%,transparent)}}}.cbi-dropdown>ul.preview{display:none}.cbi-dropdown[empty]>ul{max-width:1px;max-height:1px}.cbi-dropdown[empty]>ul>li,.cbi-dropdown[optional][open]>ul.dropdown>li[placeholder]{display:block}.cbi-dropdown[open]{border-color:var(--primary);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary);position:relative}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown[open]{--tw-ring-color:color-mix(in oklab,var(--primary)20%,transparent)}}.cbi-dropdown[open]>ul.dropdown{width:auto;max-width:none;display:block}.cbi-dropdown[open]>ul.dropdown>li{display:flex}.cbi-dropdown[open]>ul.dropdown>li[selected]{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown[open]>ul.dropdown>li[selected]{background-color:color-mix(in oklab,var(--primary)20%,transparent)}}.cbi-dropdown[open]>ul.dropdown>li[selected]{color:var(--primary)}.cbi-dropdown[open]>ul.dropdown>li[unselectable]{opacity:.4}.cbi-dropdown[open]>ul.dropdown>li[unselectable]:where([data-darkmode=true],[data-darkmode=true] *){opacity:.3}.cbi-dropdown[open]>ul.dropdown>li .hide-open{display:none}.cbi-dropdown[open]>ul.dropdown>li .hide-close{display:block}.cbi-dropdown[open]>ul.preview{display:flex}:is(.cbi-dropdown[multiple][more],.cbi-dropdown[multiple][empty])>.more{padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*1.5);flex:none;display:flex}.cbi-dropdown[multiple][open]>ul.dropdown>li>form{align-items:center;display:flex}.cbi-dropdown[multiple]>ul>li>label{display:flex}.cbi-dropdown>.open{cursor:pointer;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--border);flex:none;justify-content:center;align-items:center;display:flex}@supports (color:color-mix(in lab, red, red)){.cbi-dropdown>.open{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.cbi-dropdown>.open{padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:currentColor}.cbi-dropdown>.more{display:none}.cbi-tooltip{z-index:110;max-width:var(--container-xs);--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);border-radius:calc(var(--radius-base)*1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);position:absolute}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip{border-color:color-mix(in oklab,var(--border)60%,transparent)}}.cbi-tooltip{background-color:var(--tooltip-bg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));overflow-wrap:normal;word-break:normal;white-space:normal;color:var(--foreground);opacity:0;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-out);transition-duration:.3s;transition-timing-function:var(--ease-out)}.cbi-tooltip.error{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip.error{border-color:color-mix(in oklab,var(--error)40%,transparent)}}.cbi-tooltip.error{background-color:var(--error);color:var(--error-foreground)}.cbi-tooltip.success{border-color:var(--success)}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip.success{border-color:color-mix(in oklab,var(--success)40%,transparent)}}.cbi-tooltip.success{background-color:var(--success);color:var(--success-foreground)}.cbi-tooltip.info{border-color:var(--info)}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip.info{border-color:color-mix(in oklab,var(--info)40%,transparent)}}.cbi-tooltip.info{background-color:var(--info);color:var(--info-foreground)}.cbi-tooltip.notice{border-color:var(--warning)}@supports (color:color-mix(in lab, red, red)){.cbi-tooltip.notice{border-color:color-mix(in oklab,var(--warning)40%,transparent)}}.cbi-tooltip.notice{background-color:var(--warning);color:var(--warning-foreground)}.cbi-tooltip-container,[data-tooltip]{cursor:help;white-space:nowrap;text-underline-offset:2px;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;text-decoration-line:underline;text-decoration-style:dotted;text-decoration-color:currentColor;transition-duration:.2s;display:inline-block;position:relative}:is(.cbi-tooltip-container,[data-tooltip]) .cbi-tooltip{visibility:hidden;--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);opacity:0;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-out);transition-duration:.3s;transition-timing-function:var(--ease-out)}:is(.cbi-tooltip-container,[data-tooltip]):hover .cbi-tooltip{visibility:visible;--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y);opacity:1}.alert-message{top:calc(var(--spacing)*14);z-index:40;margin-bottom:calc(var(--spacing)*4);border-radius:calc(var(--radius-base)*4);border-style:var(--tw-border-style);border-width:1px;border-color:var(--default);position:sticky}@supports (color:color-mix(in lab, red, red)){.alert-message{border-color:color-mix(in oklab,var(--default)30%,transparent)}}.alert-message{background-color:var(--default);padding:calc(var(--spacing)*6);color:var(--default-foreground)}@media not all and (min-width:48rem){.alert-message{margin-inline:calc(var(--spacing)*0);margin-bottom:calc(var(--spacing)*3);border-radius:calc(var(--radius-base)*3);padding:calc(var(--spacing)*4)}}.alert-message.modal{position:static;top:auto}.modal .alert-message{margin-bottom:calc(var(--spacing)*0)}.login-shell .alert-message{margin-bottom:calc(var(--spacing)*0);border-radius:calc(var(--radius-base)*2);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2.5);text-align:center;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}@media not all and (min-width:48rem){.login-shell .alert-message{padding-inline:calc(var(--spacing)*.5)}}.alert-message.success{border-color:var(--success)}@supports (color:color-mix(in lab, red, red)){.alert-message.success{border-color:color-mix(in oklab,var(--success)30%,transparent)}}.alert-message.success{background-color:var(--success);color:var(--success-foreground)}.alert-message.info{border-color:var(--info)}@supports (color:color-mix(in lab, red, red)){.alert-message.info{border-color:color-mix(in oklab,var(--info)30%,transparent)}}.alert-message.info{background-color:var(--info);color:var(--info-foreground)}.alert-message.warning,.alert-message.notice{border-color:var(--warning)}@supports (color:color-mix(in lab, red, red)){.alert-message.warning,.alert-message.notice{border-color:color-mix(in oklab,var(--warning)30%,transparent)}}.alert-message.warning,.alert-message.notice{background-color:var(--warning);color:var(--warning-foreground)}.alert-message.error,.alert-message.danger{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.alert-message.error,.alert-message.danger{border-color:color-mix(in oklab,var(--error)30%,transparent)}}.alert-message.error,.alert-message.danger{background-color:var(--error);color:var(--error-foreground)}.alert-message h4,.alert-message h5,.alert-message pre,.alert-message ul,.alert-message li,.alert-message p{border-style:var(--tw-border-style);color:inherit;background-color:#0000;border-width:0}.alert-message h4{margin-bottom:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}@media not all and (min-width:48rem){.alert-message h4{margin-bottom:calc(var(--spacing)*1.5);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.alert-message p{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}@media not all and (min-width:48rem){.alert-message p{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}.alert-message .right{margin-top:calc(var(--spacing)*4)}@media not all and (min-width:48rem){.alert-message .right{margin-top:calc(var(--spacing)*3)}}.cbi-progressbar{height:calc(var(--spacing)*3.5);cursor:help;background-color:var(--secondary);border-radius:3.40282e38px;width:100%;position:relative;overflow:hidden}.cbi-progressbar:before{content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);content:var(--tw-content);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);content:var(--tw-content);border-radius:calc(var(--radius-base)*2);content:var(--tw-content);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));content:var(--tw-content);white-space:nowrap;content:var(--tw-content);color:var(--foreground);--tw-content:attr(title);content:var(--tw-content);position:absolute;top:50%;left:50%}@media not all and (min-width:48rem){.cbi-progressbar{height:calc(var(--spacing)*4);border-radius:calc(var(--radius-base)*2)}.cbi-progressbar:before{content:var(--tw-content);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));content:var(--tw-content);--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}}@media not all and (min-width:40rem){[data-page=admin-system-package-manager] .cbi-progressbar:before{content:var(--tw-content);font-size:10px}}.cbi-progressbar>div{--tw-gradient-position:to right;height:100%}@supports (background-image:linear-gradient(in lab, red, red)){.cbi-progressbar>div{--tw-gradient-position:to right in oklab}}.cbi-progressbar>div{background-image:linear-gradient(var(--tw-gradient-stops));--tw-gradient-from:var(--progress-bar-start);--tw-gradient-to:var(--progress-bar-end);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}.label{background-color:var(--default);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--default-foreground);text-transform:uppercase;border-radius:3.40282e38px}.label.important{background-color:var(--info);color:var(--info-foreground)}.label.warning{background-color:var(--error);color:var(--error-foreground)}.label.success{background-color:var(--success);color:var(--success-foreground)}.label.notice{background-color:var(--warning);color:var(--warning-foreground)}.zonebadge{align-items:center;gap:calc(var(--spacing)*1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--secondary);background-color:var(--secondary);padding-inline:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--secondary-foreground);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-radius:3.40282e38px;display:inline-flex;overflow:visible}@media (hover:hover){.zonebadge:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){.zonebadge{gap:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.zonebadge[style*=--zone-color-rgb]{background-color:rgb(var(--zone-color-rgb),.7)!important}.zonebadge[style*=--zone-color-rgb] em{color:var(--default-foreground)}.cbi-dropdown .zonebadge{border-radius:calc(var(--radius-base)*1.5);padding-inline:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*.5)}.ifacebadge{cursor:default;align-items:center;gap:calc(var(--spacing)*2);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--label-surface);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--default-foreground);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);flex-wrap:wrap;display:inline-flex}@media (hover:hover){.ifacebadge:hover{border-color:var(--border);background-color:var(--label-surface);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){.ifacebadge{gap:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.cbi-dropdown .ifacebadge,.cbi-tooltip .ifacebadge{flex-wrap:nowrap}.zonebadge>.ifacebadge,.cbi-dropdown .ifacebadge{border-radius:calc(var(--radius-base)*2);padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*0)}:is(.zonebadge>.ifacebadge,.cbi-dropdown .ifacebadge) img{margin-right:calc(var(--spacing)*1);width:calc(var(--spacing)*4)}.ifacebadge>img{width:calc(var(--spacing)*5);flex-shrink:0;align-self:center}@media not all and (min-width:48rem){.ifacebadge>img{width:calc(var(--spacing)*4)}}.ifacebadge>span>.nowrap{margin-bottom:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));display:inline-block}@media not all and (min-width:48rem){.ifacebadge>span>.nowrap{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.ifacebadge.ifacebadge-active{border-color:var(--primary);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.ifacebadge.large{min-width:200px;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);flex:1}@media not all and (min-width:48rem){.ifacebadge.large{min-width:180px;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2.5)}}.cbi-section{margin-bottom:calc(var(--spacing)*3);border-radius:calc(var(--radius-base)*2);padding:calc(var(--spacing)*5);overflow:visible}@media not all and (min-width:48rem){.cbi-section{padding:calc(var(--spacing)*3)}}.cbi-section[data-tab-title]{margin:calc(var(--spacing)*0);padding:calc(var(--spacing)*0)}.cbi-section[data-tab-active=true]{margin-bottom:calc(var(--spacing)*3);padding:calc(var(--spacing)*5)}@media not all and (min-width:48rem){.cbi-section[data-tab-active=true]{padding:calc(var(--spacing)*3)}}#maincontent .cbi-section{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#maincontent .cbi-section{border-color:color-mix(in oklab,var(--border)30%,transparent)}}#maincontent .cbi-section{background-color:var(--panel-bg);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){#maincontent .cbi-section:hover{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#maincontent .cbi-section:hover{border-color:color-mix(in oklab,var(--border)50%,transparent)}}#maincontent .cbi-section:hover{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.cbi-section>.cbi-title{justify-content:space-between;align-items:flex-start;display:flex}.cbi-section>.cbi-title>h3{flex:1}.cbi-section>.cbi-title>h3 .label[data-clickable]{background-color:var(--label-surface);color:var(--foreground);width:calc(var(--spacing)*7);height:calc(var(--spacing)*7);cursor:pointer;padding:calc(var(--spacing)*0);justify-content:center;align-items:center;display:inline-flex}.cbi-section>.cbi-title>h3 .label[data-clickable]:before{content:var(--tw-content);width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);content:var(--tw-content);content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));content:var(--tw-content);--tw-duration:.3s;background-color:currentColor;transition-duration:.3s}@media (hover:hover){.cbi-section>.cbi-title>h3 .label[data-clickable]:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.cbi-section>.cbi-title>h3 .label[data-clickable]:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}@media not all and (min-width:48rem){.cbi-section>.cbi-title>h3 .label[data-clickable]{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status]{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);cursor:pointer;font-size:0;position:relative}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status]:before{content:var(--tw-content);content:var(--tw-content);width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);content:var(--tw-content);content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));content:var(--tw-content);--tw-duration:.3s;content:var(--tw-content);--tw-ease:var(--ease-out);transition-duration:.3s;transition-timing-function:var(--ease-out);background-color:currentColor;position:absolute}@media (hover:hover){.cbi-section>.cbi-title>h3 span[data-indicator=poll-status]:hover:before{content:var(--tw-content);--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status]:active:before{content:var(--tw-content);--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status][data-style=active]:before{content:var(--tw-content);content:var(--tw-content);rotate:none;-webkit-mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M8.22%205.22a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L11.94%2010L8.22%206.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M8.22%205.22a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L11.94%2010L8.22%206.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat}.cbi-section>.cbi-title>h3 span[data-indicator=poll-status][data-style=inactive]:before{content:var(--tw-content);content:var(--tw-content);rotate:90deg;-webkit-mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M8.22%205.22a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L11.94%2010L8.22%206.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M8.22%205.22a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L11.94%2010L8.22%206.28a.75.75%200%200%201%200-1.06'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat}.cbi-section legend{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.cbi-section h3{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.cbi-section h3{border-color:color-mix(in oklab,var(--border)50%,transparent)}}.cbi-section h3{padding-bottom:calc(var(--spacing)*4);color:var(--foreground)}@media not all and (min-width:48rem){.cbi-section h3{margin-inline:calc(var(--spacing)*0);padding-bottom:calc(var(--spacing)*2)}.cbi-section div[style*=display\:grid]{gap:calc(var(--spacing)*3);grid-template-columns:repeat(2,minmax(0,1fr))!important}}.cbi-section .network-status-table{margin-bottom:calc(var(--spacing)*3);justify-content:space-around;gap:calc(var(--spacing)*4);flex-wrap:wrap;display:flex}@media not all and (min-width:48rem){.cbi-section .network-status-table{flex-direction:column}}.cbi-section .cbi-section-create{margin-top:calc(var(--spacing)*6);align-items:center;gap:calc(var(--spacing)*3);flex-wrap:wrap;display:flex}@media not all and (min-width:48rem){.cbi-section .cbi-section-create{margin-inline:calc(var(--spacing)*0);margin-top:calc(var(--spacing)*5);align-items:stretch;gap:calc(var(--spacing)*3);flex-direction:column}}.cbi-section .cbi-section-create>div{flex:none}@media not all and (min-width:48rem){.cbi-section .cbi-section-create>div{flex:1;width:100%}}.ifacebox{min-width:calc(var(--spacing)*40);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--panel-bg);flex-direction:column;align-items:stretch;display:inline-flex;position:relative;overflow:visible}@supports (color:color-mix(in lab, red, red)){.ifacebox{background-color:color-mix(in oklab,var(--panel-bg)95%,transparent)}}.ifacebox{text-align:center;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){.ifacebox:hover{border-color:var(--border);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){.ifacebox{min-width:calc(var(--spacing)*30);border-radius:calc(var(--radius-base)*3);border-color:var(--border);flex:1}@supports (color:color-mix(in lab, red, red)){.ifacebox{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.ifacebox{background-color:var(--panel-bg)}@supports (color:color-mix(in lab, red, red)){.ifacebox{background-color:color-mix(in oklab,var(--panel-bg)98%,transparent)}}.ifacebox{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}td .ifacebox{flex-direction:row}}.ifacebox .ifacebox-head{border-top-left-radius:calc(var(--radius-base)*2);border-top-right-radius:calc(var(--radius-base)*2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);background-color:var(--label-surface);width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);text-align:center;--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--foreground);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}@media not all and (min-width:48rem){.ifacebox .ifacebox-head{border-top-left-radius:calc(var(--radius-base)*3);border-top-right-radius:calc(var(--radius-base)*3);border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.ifacebox .ifacebox-head{border-color:color-mix(in oklab,var(--border)80%,transparent)}}.ifacebox .ifacebox-head{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}}.ifacebox .ifacebox-head[style*=--zone-color-rgb]{background-color:rgb(var(--zone-color-rgb),.75)!important}@media not all and (min-width:48rem){td :is(.ifacebox .ifacebox-head){border-top-left-radius:calc(var(--radius-base)*3);border-bottom-left-radius:calc(var(--radius-base)*3);border-right-style:var(--tw-border-style);border-right-width:1px;border-bottom-style:var(--tw-border-style);border-bottom-width:0;border-top-right-radius:0;flex-shrink:0;justify-content:center;align-items:center;width:auto;display:flex}}.ifacebox .ifacebox-head.cbi-tooltip-container{background-color:#0000;position:static}.ifacebox .ifacebox-head.cbi-tooltip-container .cbi-tooltip{bottom:calc(var(--spacing)*0);left:calc(var(--spacing)*0)}.ifacebox .ifacebox-head.active{border-color:var(--primary);background-color:var(--primary);color:var(--primary-foreground)}.ifacebox .ifacebox-body{justify-content:space-around;align-items:center;gap:calc(var(--spacing)*2);border-bottom-right-radius:calc(var(--radius-base)*2);border-bottom-left-radius:calc(var(--radius-base)*2);width:100%;padding:calc(var(--spacing)*4);text-align:center;color:var(--default-foreground);flex-direction:column;flex:1;display:flex}@media not all and (min-width:48rem){.ifacebox .ifacebox-body{border-bottom-right-radius:calc(var(--radius-base)*3);border-bottom-left-radius:calc(var(--radius-base)*3)}td :is(.ifacebox .ifacebox-body){border-top-right-radius:calc(var(--radius-base)*3);border-bottom-right-radius:calc(var(--radius-base)*3);padding-block:calc(var(--spacing)*2);padding-right:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*4);border-bottom-left-radius:0;flex-direction:row}}:where(.ifacebox .ifacebox-body>span>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1.5)*calc(1 - var(--tw-space-y-reverse)))}.ifacebox .ifacebox-body>span{color:var(--foreground)}@media not all and (min-width:48rem){:where(.ifacebox .ifacebox-body>span>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}.ifacebox .ifacebox-body>span{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}}.network-status-table :is(.ifacebox .ifacebox-body>span){width:100%}.ifacebox .ifacebox-body>span .cbi-tooltip-container+.cbi-tooltip-container{margin-left:calc(var(--spacing)*2)}@media not all and (min-width:48rem){.ifacebox .ifacebox-body>span .cbi-tooltip-container+.cbi-tooltip-container{margin-left:calc(var(--spacing)*1.5)}}.ifacebox .ifacebox-body>span .cbi-tooltip{--tw-animation-delay:.2s;transition-delay:.2s;animation-delay:.2s}.ifacebox .ifacebox-body>span .nowrap{display:inline-block}.ifacebox .ifacebox-body>span>.nowrap:not(:last-child){margin-bottom:calc(var(--spacing)*4)}@media not all and (min-width:48rem){.ifacebox .ifacebox-body>span>.nowrap:not(:last-child){margin-bottom:calc(var(--spacing)*3)}}.ifacebox .ifacebox-body>span img{width:calc(var(--spacing)*6);flex-shrink:0}@media not all and (min-width:48rem){.ifacebox .ifacebox-body>span img{width:calc(var(--spacing)*5)}}.ifacebox .ifacebox-body>div{flex-wrap:wrap;width:100%;display:flex}:where(.ifacebox .ifacebox-body>div>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2.5)*calc(1 - var(--tw-space-y-reverse)))}@media not all and (min-width:48rem){:where(.ifacebox .ifacebox-body>div>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1.5)*calc(1 - var(--tw-space-y-reverse)))}td :is(.ifacebox .ifacebox-body>div){flex:1;width:auto}:where(td :is(.ifacebox .ifacebox-body>div)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}}.ifacebox .ifacebox-body>div .ifacebadge{flex:1}.ifacebox .ifacebox-body .cbi-tooltip-container{cursor:help;color:var(--foreground);justify-content:center;align-items:center;display:inline-flex;position:static}.ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip{bottom:calc(var(--spacing)*0)}@media (min-width:48rem){.ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip{left:calc(var(--spacing)*0)}}.ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip .nowrap{white-space:nowrap}.ifacebox .ifacebox-body small{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:calc(var(--spacing)*4);line-height:calc(var(--spacing)*4);display:block}@media not all and (min-width:48rem){td :is(.ifacebox .ifacebox-body small){margin-top:calc(var(--spacing)*0)}}#modal_overlay{pointer-events:none;top:calc(var(--spacing)*0);bottom:calc(var(--spacing)*0);background-color:#0000;display:none;position:fixed;overflow:auto}.modal-overlay-active #modal_overlay{pointer-events:auto;inset:calc(var(--spacing)*0);right:calc(var(--spacing)*0);left:calc(var(--spacing)*0);z-index:100;background-color:var(--overlay-base);grid-template-columns:repeat(1,minmax(0,1fr));place-items:center;display:grid}@supports (color:color-mix(in lab, red, red)){.modal-overlay-active #modal_overlay{background-color:color-mix(in oklab,var(--overlay-base)70%,transparent)}}.modal-overlay-active #modal_overlay{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}#modal_overlay>.modal{width:var(--container-5xl);gap:calc(var(--spacing)*4);border-radius:calc(var(--radius-base)*3);border-style:var(--tw-border-style);border-width:1px;border-color:color-mix(in oklab,var(--border)70%,transparent);background-color:var(--page-bg);padding:calc(var(--spacing)*6);overflow-wrap:break-word;white-space:normal;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-lg));--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);flex-direction:column;display:flex}@media not all and (min-width:48rem){#modal_overlay>.modal{gap:calc(var(--spacing)*3);width:100%;padding:calc(var(--spacing)*4)}}#modal_overlay>.modal h4{margin-bottom:calc(var(--spacing)*0);text-align:center}#modal_overlay>.modal h4 em{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--primary)}#modal_overlay>.modal h5{margin-block:calc(var(--spacing)*3);color:var(--foreground)}#modal_overlay>.modal p{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--default-foreground)}#modal_overlay>.modal>ul{border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);overflow:auto}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal>ul{border-color:color-mix(in oklab,var(--border)60%,transparent)}}#modal_overlay>.modal>ul{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal>ul{background-color:color-mix(in oklab,var(--page-bg)60%,transparent)}}#modal_overlay>.modal>ul{padding:calc(var(--spacing)*3)}#modal_overlay>.modal label.btn{border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal label.btn{border-color:color-mix(in oklab,var(--border)60%,transparent)}}#modal_overlay>.modal label.btn{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal label.btn{background-color:color-mix(in oklab,var(--page-bg)60%,transparent)}}#modal_overlay>.modal label.btn{color:var(--foreground)}@media (hover:hover){#modal_overlay>.modal label.btn:hover{border-color:var(--border)}}#modal_overlay>.modal pre{overflow:auto}#modal_overlay>.modal pre.errors{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal pre.errors{border-color:color-mix(in oklab,var(--error)60%,transparent)}}#modal_overlay>.modal pre.errors{background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal pre.errors{background-color:color-mix(in oklab,var(--error)20%,transparent)}}#modal_overlay>.modal pre.errors{color:var(--error-foreground)}#modal_overlay>.modal pre+pre{margin-top:calc(var(--spacing)*3)}#modal_overlay>.modal .button-row,#modal_overlay>.modal div.left,#modal_overlay>.modal div.right{gap:calc(var(--spacing)*3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--border);flex-shrink:0;display:flex}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal .button-row,#modal_overlay>.modal div.left,#modal_overlay>.modal div.right{border-color:color-mix(in oklab,var(--border)40%,transparent)}}#modal_overlay>.modal .button-row,#modal_overlay>.modal div.left,#modal_overlay>.modal div.right{padding:calc(var(--spacing)*4)}@media not all and (min-width:48rem){#modal_overlay>.modal .button-row,#modal_overlay>.modal div.left,#modal_overlay>.modal div.right{gap:calc(var(--spacing)*1.5);padding:calc(var(--spacing)*2.5)}}#modal_overlay>.modal div.left{justify-content:flex-start}#modal_overlay>.modal .button-row,#modal_overlay>.modal div.right{justify-content:flex-end}#modal_overlay>.modal.uci-dialog ins,#modal_overlay>.modal.uci-dialog del,#modal_overlay>.modal.uci-dialog var{font-family:var(--font-mono);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);font-style:normal;text-decoration-line:none}#modal_overlay>.modal.uci-dialog ins{border-style:var(--tw-border-style);border-width:1px;border-color:var(--success);background-color:var(--success)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog ins{background-color:color-mix(in oklab,var(--success)80%,transparent)}}#modal_overlay>.modal.uci-dialog ins{color:var(--success-foreground)}#modal_overlay>.modal.uci-dialog del{border-style:var(--tw-border-style);border-width:1px;border-color:var(--error);background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog del{background-color:color-mix(in oklab,var(--error)80%,transparent)}}#modal_overlay>.modal.uci-dialog del{color:var(--error-foreground)}#modal_overlay>.modal.uci-dialog var{border-style:var(--tw-border-style);border-width:1px;border-color:var(--info);background-color:var(--info)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog var{background-color:color-mix(in oklab,var(--info)80%,transparent)}}#modal_overlay>.modal.uci-dialog var{color:var(--info-foreground)}#modal_overlay>.modal.uci-dialog .uci-change-legend{margin-top:calc(var(--spacing)*4);gap:calc(var(--spacing)*3);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog .uci-change-legend{border-color:color-mix(in oklab,var(--border)60%,transparent)}}#modal_overlay>.modal.uci-dialog .uci-change-legend{background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){#modal_overlay>.modal.uci-dialog .uci-change-legend{background-color:color-mix(in oklab,var(--page-bg)60%,transparent)}}#modal_overlay>.modal.uci-dialog .uci-change-legend{padding:calc(var(--spacing)*4)}@media not all and (min-width:48rem){#modal_overlay>.modal.uci-dialog .uci-change-legend{gap:calc(var(--spacing)*2);padding:calc(var(--spacing)*3);grid-template-columns:repeat(1,minmax(0,1fr))}}#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label{align-items:center;gap:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--foreground);display:flex}#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label ins,#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label del,#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label var{height:calc(var(--spacing)*6);min-width:calc(var(--spacing)*6);border-radius:var(--radius-base);padding-inline:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);justify-content:center;align-items:center;display:flex}#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label var ins,#modal_overlay>.modal.uci-dialog .uci-change-legend .uci-change-legend-label var del{margin-left:calc(var(--spacing)*1);height:calc(var(--spacing)*4);min-width:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*1)}#modal_overlay>.modal.uci-dialog .uci-change-list{margin-top:calc(var(--spacing)*4)}:where(#modal_overlay>.modal.uci-dialog .uci-change-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}#modal_overlay>.modal.uci-dialog .uci-change-list h5{margin-top:calc(var(--spacing)*6);margin-bottom:calc(var(--spacing)*2);border-radius:calc(var(--radius-base)*1.5);background-color:var(--default);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--default-foreground)}#modal_overlay>.modal.uci-dialog .uci-change-list h5:first-child{margin-top:calc(var(--spacing)*0)}#modal_overlay>.modal.uci-dialog .uci-change-list ins,#modal_overlay>.modal.uci-dialog .uci-change-list del{border-radius:calc(var(--radius-base)*1.5);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));display:block}:is(#modal_overlay>.modal.uci-dialog .uci-change-list ins,#modal_overlay>.modal.uci-dialog .uci-change-list del) strong{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}#modal_overlay>.modal.uci-dialog .uci-change-list var{border-radius:var(--radius-base);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));display:inline-block}#modal_overlay>.modal.uci-dialog .uci-change-list var ins,#modal_overlay>.modal.uci-dialog .uci-change-list var del{margin-left:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));display:inline-block}@media not all and (min-width:48rem){.mobile-menu-overlay{visibility:hidden;inset:calc(var(--spacing)*0);z-index:60;background-color:var(--overlay-base);position:fixed}@supports (color:color-mix(in lab, red, red)){.mobile-menu-overlay{background-color:color-mix(in oklab,var(--overlay-base)60%,transparent)}}.mobile-menu-overlay{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}}@media (min-width:48rem){.mobile-menu-overlay{display:none}}@media not all and (min-width:48rem){.mobile-menu-overlay.mobile-menu-open{visibility:visible;opacity:1;--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.mobile-menu-overlay.mobile-menu-open .mobile-nav{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.mobile-menu-overlay .mobile-nav{top:calc(var(--spacing)*0);right:calc(var(--spacing)*0);height:100%;width:calc(var(--spacing)*80);--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y);background-color:var(--page-bg);--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);--tw-backdrop-saturate:saturate(150%);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;flex-direction:column;transition-duration:.3s;display:flex;position:absolute}.mobile-menu-overlay .mobile-nav .mobile-nav-header{padding:calc(var(--spacing)*6);padding-bottom:calc(var(--spacing)*4);justify-content:space-between;align-items:center;display:flex}.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-title{margin:calc(var(--spacing)*0);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--foreground)}.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close{cursor:pointer;transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}@media (hover:hover){.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close svg{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}@media not all and (min-width:48rem){.mobile-menu-overlay .mobile-nav .mobile-nav-header .mobile-nav-close svg{color:var(--foreground);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}.mobile-menu-overlay .mobile-nav .mobile-nav-list{margin:calc(var(--spacing)*0);gap:calc(var(--spacing)*0);padding:calc(var(--spacing)*0);padding-inline:calc(var(--spacing)*6);flex-direction:column;flex:1;list-style-type:none;display:flex;overflow-y:auto}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-link{width:100%;padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*4);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--default-foreground);transition-property:color,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;align-items:center;text-decoration-line:none;transition-duration:.3s;display:flex}@media (hover:hover){.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-link:hover{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y);color:var(--foreground)}}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu{margin:calc(var(--spacing)*0);padding:calc(var(--spacing)*0);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-in-out);transition-duration:.3s;transition-timing-function:var(--ease-in-out);list-style-type:none;overflow:hidden}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem{border-color:color-mix(in oklab,var(--border)20%,transparent)}}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem .mobile-nav-sublink{width:100%;padding-block:calc(var(--spacing)*3);padding-right:calc(var(--spacing)*0);padding-left:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--default-foreground);transition-property:color,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;align-items:center;text-decoration-line:none;transition-duration:.3s;display:flex}@media (hover:hover){.mobile-menu-overlay .mobile-nav .mobile-nav-list .mobile-nav-item .mobile-nav-submenu .mobile-nav-subitem .mobile-nav-sublink:hover{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y);color:var(--foreground)}}}[data-nav-type=mega-menu] .desktop-menu-overlay{pointer-events:none;inset:calc(var(--spacing)*0);z-index:50;background-color:var(--overlay-base);position:fixed}@supports (color:color-mix(in lab, red, red)){[data-nav-type=mega-menu] .desktop-menu-overlay{background-color:color-mix(in oklab,var(--overlay-base)35%,transparent)}}[data-nav-type=mega-menu] .desktop-menu-overlay{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}@media not all and (min-width:48rem){[data-nav-type=mega-menu] .desktop-menu-overlay{display:none}}[data-nav-type=mega-menu] .desktop-menu-overlay.active{pointer-events:auto;opacity:1;--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}table.table,.table{margin-bottom:calc(var(--spacing)*3);border-collapse:separate;--tw-border-spacing-x:calc(var(--spacing)*0);--tw-border-spacing-y:calc(var(--spacing)*0);width:100%;border-spacing:var(--tw-border-spacing-x)var(--tw-border-spacing-y);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);overflow:visible}@media not all and (min-width:48rem){table.table,.table{margin-inline:calc(var(--spacing)*0);border-style:var(--tw-border-style);background-color:var(--panel-bg);border-width:0;width:100%;display:block;overflow:visible}@supports (color:color-mix(in lab, red, red)){table.table,.table{background-color:color-mix(in oklab,var(--panel-bg)98%,transparent)}}table.table,.table{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}:is(table.table,.table)[width="100%"]{width:100%}:is(table.table,.table)[width="33%"]{width:33.3333%}:is(table.table,.table) .cbi-section-table-titles{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):before{content:var(--tw-content);content:var(--tw-content);border-color:var(--border);content:var(--tw-content);padding-inline:calc(var(--spacing)*3);content:var(--tw-content);padding-block:calc(var(--spacing)*3);content:var(--tw-content);vertical-align:middle;content:var(--tw-content);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));content:var(--tw-content);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-content:attr(data-title);content:var(--tw-content);content:var(--tw-content);display:table-cell}@media not all and (min-width:48rem){:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):before{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));flex:0 0 100%;display:flex}}:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):before{content:var(--tw-content)}@media (min-width:48rem){:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):before{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}}:is(:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):not([data-title]),:is(:is(table.table,.table) .cbi-section-table-titles.named,:is(table.table,.table) .cbi-section-table-descr.named,:is(table.table,.table) .cbi-section-table-row[data-title]):has(td.cbi-section-table-titles)):before{content:var(--tw-content);--tw-content:none;content:none}:is(table.table,.table) .cbi-section-table-titles.named:before{content:var(--tw-content);background-color:var(--label-surface);content:var(--tw-content)}@media not all and (min-width:48rem){:is(table.table,.table) .cbi-section-table-titles.named:before{background-color:var(--label-surface)}}:is(table.table,.table) .cbi-section-table-titles.named:before{content:var(--tw-content)}@media (min-width:48rem){:is(table.table,.table) .cbi-section-table-titles.named:before{border-top-left-radius:calc(var(--radius-base)*2)}}@media not all and (min-width:48rem){:is(table.table,.table) tbody,:is(table.table,.table) .tbody{display:block}}@media (hover:hover){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):hover{background-color:var(--label-surface)}}@media not all and (min-width:48rem){:is(table.table,.table) tr,:is(table.table,.table) .tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);flex-wrap:wrap;display:flex}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):has(.cbi-progressbar){justify-content:center;align-items:center;display:flex}}@media (min-width:48rem){:is(:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child:not([data-title]) .th,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child:not([data-title]) .td):first-child{border-top-left-radius:calc(var(--radius-base)*2)}:is(:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child .th,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child .td):last-child{border-top-right-radius:calc(var(--radius-base)*2)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child{border-bottom-right-radius:calc(var(--radius-base)*2);border-bottom-left-radius:calc(var(--radius-base)*2)}}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child[data-title]:before{content:var(--tw-content);border-bottom-left-radius:calc(var(--radius-base)*2);content:var(--tw-content);border-bottom-style:var(--tw-border-style);border-bottom-width:0}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child .td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child .td:first-child{border-bottom-left-radius:calc(var(--radius-base)*2)}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):last-child .td:last-child{border-bottom-right-radius:calc(var(--radius-base)*2)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).table-titles,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-section-table-titles,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-section-table-descr{display:none}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr):first-child:not(.table-titles,.cbi-section-table-titles,.cbi-section-table-descr),:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).table-titles+.tr,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-section-table-titles+.tr,:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-section-table-descr+.tr{border-top-left-radius:calc(var(--radius-base)*2);border-top-right-radius:calc(var(--radius-base)*2)}}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-rowstyle-1{background-color:var(--panel-bg)}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-rowstyle-2{background-color:var(--label-surface)}@supports (color:color-mix(in lab, red, red)){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr).cbi-rowstyle-2{background-color:color-mix(in oklab,var(--label-surface)50%,transparent)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-1{flex:2rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-1{width:calc(var(--spacing)*20);max-width:calc(var(--spacing)*28)}}:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-2{white-space:normal}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-2{flex:2 2 4rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-2{width:calc(var(--spacing)*32);max-width:calc(var(--spacing)*40)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-3{flex:3 3 6rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-3{width:calc(var(--spacing)*44);max-width:calc(var(--spacing)*52)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-4{flex:4 4 8rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-4{width:calc(var(--spacing)*56);max-width:calc(var(--spacing)*64)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-5{flex:5 5 10rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-5{width:calc(var(--spacing)*68);max-width:calc(var(--spacing)*76)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-6{flex:6 6 12rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-6{width:calc(var(--spacing)*80);max-width:calc(var(--spacing)*88)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-7{flex:7 7 14rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-7{width:calc(var(--spacing)*92);max-width:calc(var(--spacing)*100)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-8{flex:8 8 16rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-8{width:calc(var(--spacing)*104);max-width:calc(var(--spacing)*112)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-9{flex:9 9 18rem}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-9{width:calc(var(--spacing)*116);max-width:calc(var(--spacing)*124)}}@media not all and (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-10{flex:0 0 100%;max-width:calc(100vw - 40px)}}@media (min-width:48rem){:is(:is(table.table,.table) tr,:is(table.table,.table) .tr)>.col-10{width:calc(var(--spacing)*128);max-width:calc(var(--spacing)*136)}}:is(table.table,.table) th,:is(table.table,.table) .th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);background-color:var(--label-surface);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);text-align:left;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}@media not all and (min-width:48rem){:is(table.table,.table) th,:is(table.table,.table) .th{border-style:var(--tw-border-style);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:0;flex:50%}}#cbi-samba4 :is(:is(table.table,.table) th,:is(table.table,.table) .th){padding-inline:calc(var(--spacing)*.5);padding-block:calc(var(--spacing)*1.5)}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[width="100%"]{width:100%}@media not all and (min-width:48rem){:is(:is(table.table,.table) th,:is(table.table,.table) .th)[width="100%"]{flex:0 0 100%}}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[width="33%"]{width:33.3333%}@media not all and (min-width:48rem){:is(:is(table.table,.table) th,:is(table.table,.table) .th)[width="33%"]{flex:0 0 33.333%}}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true]{cursor:pointer;-webkit-user-select:none;user-select:none}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true]:after{content:var(--tw-content);margin-left:calc(var(--spacing)*.5);content:var(--tw-content);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}@media (hover:hover){:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true]:hover{background-color:var(--label-surface)}}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true][data-sort-direction=asc]:after{--tw-content:"▲";content:var(--tw-content)}:is(:is(table.table,.table) th,:is(table.table,.table) .th)[data-sortable-row=true][data-sort-direction=desc]:after{--tw-content:"▼";content:var(--tw-content)}:is(table.table,.table)[id*=status_leases] .td{max-width:calc(var(--spacing)*62)}@media not all and (min-width:48rem){:is(table.table,.table)[id*=status_leases] .td{max-width:100%}}:is(table.table,.table) td,:is(table.table,.table) .td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);vertical-align:middle;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));overflow-wrap:break-word;white-space:normal;display:table-cell}@media not all and (min-width:48rem){:is(table.table,.table) td,:is(table.table,.table) .td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:0;flex:50%}}:is(:is(table.table,.table) td,:is(table.table,.table) .td):before{content:var(--tw-content);--tw-content:attr(data-title);content:var(--tw-content);content:var(--tw-content);display:none}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td):before{margin-bottom:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);display:block}}:is(:is(table.table,.table) td,:is(table.table,.table) .td):before{content:var(--tw-content)}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td):before{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}}#cbi-samba4 :is(:is(table.table,.table) td,:is(table.table,.table) .td){padding-inline:calc(var(--spacing)*.5);padding-block:calc(var(--spacing)*1.5)}[data-page=admin-system-mounts] :is(:is(table.table,.table) td,:is(table.table,.table) .td){max-width:calc(var(--spacing)*104)}@media not all and (min-width:48rem){[data-page=admin-system-mounts] :is(:is(table.table,.table) td,:is(table.table,.table) .td){max-width:100%}#wifi_assoclist_table :is(:is(table.table,.table) td,:is(table.table,.table) .td){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);flex:0 0 100%}}#wifi_assoclist_table :is(:is(table.table,.table) td,:is(table.table,.table) .td):before{content:var(--tw-content)}@media not all and (min-width:48rem){#wifi_assoclist_table :is(:is(table.table,.table) td,:is(table.table,.table) .td):before{margin-bottom:calc(var(--spacing)*1.5)}}:is(:is(table.table,.table) td,:is(table.table,.table) .td)>input[type=text],:is(:is(table.table,.table) td,:is(table.table,.table) .td)>input[type=password],:is(:is(table.table,.table) td,:is(table.table,.table) .td)>select,:is(:is(table.table,.table) td,:is(table.table,.table) .td)>.cbi-dropdown:not(.btn):not(.cbi-button){width:100%}:is(:is(table.table,.table) td,:is(table.table,.table) .td).cbi-value-field{word-break:break-all}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td).cbi-section-actions{flex:0 0 100%}:is(:is(table.table,.table) td,:is(table.table,.table) .td).cbi-section-actions>div{width:100%}:is(:is(table.table,.table) td,:is(table.table,.table) .td).cbi-section-actions>div>.cbi-button{flex:1}:is(:is(table.table,.table) td,:is(table.table,.table) .td)>.cbi-button{width:100%}}:is(:is(table.table,.table) td,:is(table.table,.table) .td)[width="100%"]{width:100%}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td)[width="100%"]{flex:0 0 100%}}:is(:is(table.table,.table) td,:is(table.table,.table) .td)[width="33%"]{width:33.3333%}@media not all and (min-width:48rem){:is(:is(table.table,.table) td,:is(table.table,.table) .td)[width="33%"]{flex:0 0 33.333%}[data-page=admin-network-network] :is(:is(table.table,.table) td,:is(table.table,.table) .td)[data-name=_ifacebox],[data-page=admin-network-network] :is(:is(table.table,.table) td,:is(table.table,.table) .td)[data-name=_ifacestat]{flex-basis:100%}}.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell{width:auto!important}.cbi-input-invalid,.cbi-value-error{border-color:var(--error)!important}@supports (color:color-mix(in lab, red, red)){.cbi-input-invalid,.cbi-value-error{border-color:color-mix(in oklab,var(--error)70%,transparent)!important}}:is(.cbi-input-invalid,.cbi-value-error):focus{--tw-ring-color:var(--error)!important}@supports (color:color-mix(in lab, red, red)){:is(.cbi-input-invalid,.cbi-value-error):focus{--tw-ring-color:color-mix(in oklab,var(--error)40%,transparent)!important}}.cbi-value{gap:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*1.5);flex-flow:wrap;display:flex}@media not all and (min-width:48rem){.cbi-value{gap:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*1);flex-direction:column}}.cbi-value.hidden{display:none}.cbi-value+.cbi-value{margin-top:calc(var(--spacing)*3)}@media not all and (min-width:48rem){.cbi-value+.cbi-value{margin-top:calc(var(--spacing)*2)}}.cbi-value>.cbi-value-title{padding-top:calc(var(--spacing)*1);text-align:right;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}@media not all and (min-width:48rem){.cbi-value>.cbi-value-title{text-align:left;width:auto}}@media (min-width:48rem){.cbi-value>.cbi-value-title{flex:0 0 12rem}}.cbi-value>.cbi-value-field{flex:1;max-width:100%}.cbi-value>.cbi-value-field:has(>.cbi-dropdown[disabled]){cursor:not-allowed}.cbi-value>.cbi-value-field .cbi-value-description{margin-top:calc(var(--spacing)*1);padding-left:calc(var(--spacing)*4);--tw-leading:1;overflow-wrap:break-word;color:var(--muted-foreground);line-height:1;position:relative}.cbi-value>.cbi-value-field .cbi-value-description:not(:empty):before{content:var(--tw-content);content:var(--tw-content);left:calc(var(--spacing)*-.5);content:var(--tw-content);content:var(--tw-content);width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);content:var(--tw-content);background-color:var(--info-foreground);content:var(--tw-content);display:inline-block;position:absolute;-webkit-mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M15%208A7%207%200%201%201%201%208a7%207%200%200%201%2014%200M9%205a1%201%200%201%201-2%200a1%201%200%200%201%202%200M6.75%208a.75.75%200%200%200%200%201.5h.75v1.75a.75.75%200%200%200%201.5%200v-2.5A.75.75%200%200%200%208.25%208z'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%20width='24'%20height='24'%20fill='%23000000'%20style='opacity:1;'%3e%3cpath%20fill-rule='evenodd'%20d='M15%208A7%207%200%201%201%201%208a7%207%200%200%201%2014%200M9%205a1%201%200%201%201-2%200a1%201%200%200%201%202%200M6.75%208a.75.75%200%200%200%200%201.5h.75v1.75a.75.75%200%200%200%201.5%200v-2.5A.75.75%200%200%200%208.25%208z'%20clip-rule='evenodd'/%3e%3c/svg%3e") 50%/cover no-repeat}.cbi-value>.cbi-section,.cbi-value>.cbi-tblsection{width:100%}.cbi-dynlist{width:max-content;max-width:calc(var(--spacing)*120);align-items:flex-start;gap:calc(var(--spacing)*3);flex-direction:column;display:inline-flex}@media not all and (min-width:48rem){.cbi-dynlist{max-width:100%!important}}.cbi-dynlist .item{pointer-events:auto;cursor:move;align-items:flex-start;gap:calc(var(--spacing)*2);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);flex-direction:column;align-self:stretch;display:inline-flex;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.cbi-dynlist .item{border-color:color-mix(in oklab,var(--border)40%,transparent)}}.cbi-dynlist .item{background-color:var(--panel-bg);padding-block:calc(var(--spacing)*3);padding-right:calc(var(--spacing)*10);padding-left:calc(var(--spacing)*4);word-break:break-all;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);-webkit-user-select:text;user-select:text}@media not all and (min-width:48rem){.cbi-dynlist .item{width:100%;padding-block:calc(var(--spacing)*2.5);padding-right:calc(var(--spacing)*8);padding-left:calc(var(--spacing)*3)}}.cbi-dynlist .item:after{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*2.5);content:var(--tw-content);right:calc(var(--spacing)*2);content:var(--tw-content);content:var(--tw-content);height:calc(var(--spacing)*6);content:var(--tw-content);width:calc(var(--spacing)*6);content:var(--tw-content);content:var(--tw-content);cursor:pointer;content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);border-radius:calc(var(--radius-base)*1.5);content:var(--tw-content);background-color:var(--muted);content:var(--tw-content);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));content:var(--tw-content);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);content:var(--tw-content);color:var(--muted-foreground);--tw-content:"×";content:var(--tw-content);flex-shrink:0;justify-content:center;align-items:center;display:inline-flex;position:absolute}.cbi-dynlist .item.dragging{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y);cursor:grabbing;opacity:.5;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.cbi-dynlist .item.drag-over{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y);border-color:color-mix(in oklab,var(--primary)60%,transparent);background-color:color-mix(in oklab,var(--primary)5%,transparent);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--primary)30%,transparent)}.cbi-dynlist .item>span,.cbi-dynlist .item>code{pointer-events:none;border-radius:var(--radius-base);width:100%;font-family:var(--font-mono);--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.cbi-dynlist .placeholder{word-break:break-all}.cbi-dynlist .add-item{align-items:center;gap:calc(var(--spacing)*1.5);border-radius:calc(var(--radius-base)*2);border-style:var(--tw-border-style);--tw-border-style:dashed;border-style:dashed;border-width:1px;border-color:var(--border);align-self:stretch;display:inline-flex}@supports (color:color-mix(in lab, red, red)){.cbi-dynlist .add-item{border-color:color-mix(in oklab,var(--border)70%,transparent)}}.cbi-dynlist .add-item{padding-inline:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*1);background-color:#0000}@media (hover:hover){.cbi-dynlist .add-item:hover{border-color:var(--border);background-color:var(--page-bg)}@supports (color:color-mix(in lab, red, red)){.cbi-dynlist .add-item:hover{background-color:color-mix(in oklab,var(--page-bg)50%,transparent)}}}.cbi-dynlist .add-item>input{min-width:calc(var(--spacing)*61);flex:1}.cbi-dynlist .add-item>.cbi-button{min-height:calc(var(--spacing)*6);min-width:calc(var(--spacing)*6);cursor:pointer;border-radius:calc(var(--radius-base)*1.5);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*0);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);flex-shrink:0;justify-content:center;align-items:center;display:inline-flex}.cbi-dynlist .add-item>.cbi-button:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.cbi-tabmenu{margin-bottom:calc(var(--spacing)*1);align-items:center;gap:calc(var(--spacing)*1);border-radius:calc(var(--radius-base)*4);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);display:flex;overflow-x:auto}@supports (color:color-mix(in lab, red, red)){.cbi-tabmenu{border-color:color-mix(in oklab,var(--border)60%,transparent)}}.cbi-tabmenu{background-color:var(--label-surface);padding:calc(var(--spacing)*1)}.cbi-tabmenu li{margin:calc(var(--spacing)*0);flex-shrink:0;min-width:fit-content;list-style-type:none;position:relative}.cbi-tabmenu li[data-errors]:after{content:var(--tw-content);content:var(--tw-content);top:calc(var(--spacing)*-.5);content:var(--tw-content);right:calc(var(--spacing)*-1.5);content:var(--tw-content);z-index:20;content:var(--tw-content);content:var(--tw-content);min-height:calc(var(--spacing)*4);content:var(--tw-content);min-width:calc(var(--spacing)*4);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);background-color:var(--accent);content:var(--tw-content);padding-inline:calc(var(--spacing)*1.5);content:var(--tw-content);padding-block:calc(var(--spacing)*.5);content:var(--tw-content);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));content:var(--tw-content);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);content:var(--tw-content);color:var(--accent-foreground);--tw-content:attr(data-errors);content:var(--tw-content);border-radius:3.40282e38px;justify-content:center;align-items:center;display:inline-flex;position:absolute}.cbi-tabmenu li[data-errors]>a{border-style:var(--tw-border-style);border-width:1px;border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.cbi-tabmenu li[data-errors]>a{border-color:color-mix(in oklab,var(--error)50%,transparent)}}.cbi-tabmenu li>a{border-radius:calc(var(--radius-base)*4);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--muted-foreground);text-decoration-line:none;display:block}@media (hover:hover){.cbi-tabmenu li>a:hover{color:var(--foreground)}}.cbi-tabmenu li.cbi-tab a{background-color:var(--foreground);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--page-bg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[data-tab-title]{visibility:hidden;height:calc(var(--spacing)*0);overflow:hidden}[data-tab-active=true]{visibility:visible;height:auto;overflow:visible}#tabmenu{margin-bottom:calc(var(--spacing)*8);width:100%}.tabs{align-items:center;gap:calc(var(--spacing)*0);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--border);display:flex;position:relative;overflow-x:auto}.tabs a{padding-inline:calc(var(--spacing)*8);padding-block:calc(var(--spacing)*4);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--muted-foreground);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s;display:block}@media (hover:hover){.tabs a:hover{color:var(--foreground);text-decoration-line:none}}.tabs>li.active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--foreground)}.tabs>li.active>a{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--foreground)}.cbi-section-node-tabbed{margin-top:calc(var(--spacing)*6)}@media (min-width:48rem){.left{text-align:left!important}}.left:not(td){text-align:left!important}@media (min-width:48rem){.right{text-align:right!important}}.right:not(td){text-align:right!important}@media (min-width:48rem){.top{vertical-align:top!important}}.top:not(td){vertical-align:top!important}@media (min-width:48rem){.bottom{vertical-align:bottom!important}}.bottom:not(td){vertical-align:bottom!important}@media (min-width:48rem){.center{text-align:center!important}}.center:not(td){text-align:center!important}@media (min-width:48rem){.middle{vertical-align:middle!important}}.middle:not(td){vertical-align:middle!important}@media (min-width:48rem){.nowrap:not(span){white-space:nowrap}}.nowrap:not(span):not(td){white-space:nowrap}.spinning{display:inline-block;position:relative;padding-left:calc(var(--spacing)*8)!important}.spinning:before{content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);content:var(--tw-content);width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);content:var(--tw-content);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);content:var(--tw-content);animation:var(--animate-spin);content:var(--tw-content);content:var(--tw-content);background-color:currentColor;position:absolute;top:50%;left:6px;-webkit-mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1758357645269'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='7610'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M512%2085.333333c235.648%200%20426.666667%20191.018667%20426.666667%20426.666667s-191.018667%20426.666667-426.666667%20426.666667S85.333333%20747.648%2085.333333%20512%20276.352%2085.333333%20512%2085.333333z%20m0%20128a298.666667%20298.666667%200%201%200%200%20597.333334%20298.666667%20298.666667%200%200%200%200-597.333334z'%20fill='%23000000'%20fill-opacity='.05'%20p-id='7611'%3e%3c/path%3e%3cpath%20d='M813.696%20813.696c166.613333-166.613333%20166.613333-436.778667%200-603.392-166.613333-166.613333-436.778667-166.613333-603.392%200A64%2064%200%200%200%20300.8%20300.8a298.666667%20298.666667%200%201%201%20422.4%20422.4%2064%2064%200%200%200%2090.496%2090.496z'%20fill='%23000000'%20p-id='7612'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat;mask:url("data:image/svg+xml,%3c?xml%20version='1.0'%20standalone='no'?%3e%3c!DOCTYPE%20svg%20PUBLIC%20'-//W3C//DTD%20SVG%201.1//EN'%20'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3e%3csvg%20t='1758357645269'%20class='icon'%20viewBox='0%200%201024%201024'%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20p-id='7610'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='200'%20height='200'%3e%3cpath%20d='M512%2085.333333c235.648%200%20426.666667%20191.018667%20426.666667%20426.666667s-191.018667%20426.666667-426.666667%20426.666667S85.333333%20747.648%2085.333333%20512%20276.352%2085.333333%20512%2085.333333z%20m0%20128a298.666667%20298.666667%200%201%200%200%20597.333334%20298.666667%20298.666667%200%200%200%200-597.333334z'%20fill='%23000000'%20fill-opacity='.05'%20p-id='7611'%3e%3c/path%3e%3cpath%20d='M813.696%20813.696c166.613333-166.613333%20166.613333-436.778667%200-603.392-166.613333-166.613333-436.778667-166.613333-603.392%200A64%2064%200%200%200%20300.8%20300.8a298.666667%20298.666667%200%201%201%20422.4%20422.4%2064%2064%200%200%200%2090.496%2090.496z'%20fill='%23000000'%20p-id='7612'%3e%3c/path%3e%3c/svg%3e") 50%/cover no-repeat}#view>.spinning:first-child{min-height:calc(var(--spacing)*48);place-items:center;width:fit-content;margin-inline:auto;display:grid}@media not all and (min-width:48rem){#view>.spinning:first-child{min-height:calc(100dvh - 4rem)}.diag-style{gap:calc(var(--spacing)*4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}}.diag-style>div{align-items:center;gap:calc(var(--spacing)*2);flex-direction:column;display:flex}@media not all and (min-width:48rem){.diag-style>div{gap:calc(var(--spacing)*1);width:100%!important}}#file-manager-container{overflow:auto}#file-manager-container #status-bar{border-style:var(--tw-border-style);background-color:var(--page-bg);border-width:0}[data-page=admin-statistics-graphs] [data-plugin] img:where([data-darkmode=true],[data-darkmode=true] *){--tw-hue-rotate:hue-rotate(150deg);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,);--tw-invert:invert(100%)}@media not all and (min-width:48rem){[data-name=bridge-vlan]>div{overflow:visible!important}}[data-page=admin-services-openclash-config] .sub_div img{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}@media not all and (min-width:48rem){[data-page=admin-services-openclash-settings] .cbi-value-field .cbi-input-select{width:100%!important}}.Dashboard>.section-content .dashboard-bg.tr{border-radius:0}.Dashboard>.section-content .dashboard-bg{background-color:var(--panel-bg)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg{background-color:color-mix(in oklab,var(--panel-bg)95%,transparent)}}.Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg:not(.tr){border-color:color-mix(in oklab,var(--border)40%,transparent)}}.Dashboard>.section-content .dashboard-bg:not(.tr){--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){.Dashboard>.section-content .dashboard-bg:not(.tr):hover{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg:not(.tr):hover{border-color:color-mix(in oklab,var(--border)50%,transparent)}}.Dashboard>.section-content .dashboard-bg:not(.tr):hover{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){.Dashboard>.section-content .dashboard-bg{background-color:var(--panel-bg)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg{background-color:color-mix(in oklab,var(--panel-bg)98%,transparent)}}.Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.Dashboard>.section-content .dashboard-bg:not(.tr){border-color:color-mix(in oklab,var(--border)30%,transparent)}}}.Dashboard>.section-content .dashboard-bg span:where([data-darkmode=true],[data-darkmode=true] *),.Dashboard>.section-content .dashboard-bg td:where([data-darkmode=true],[data-darkmode=true] *),.Dashboard>.section-content .dashboard-bg th:where([data-darkmode=true],[data-darkmode=true] *){color:var(--foreground)!important}.Dashboard>.section-content .dashboard-bg img[src*=\.svg]:where([data-darkmode=true],[data-darkmode=true] *){--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*2)}@media not all and (min-width:48rem){.Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*1)}}.Dashboard>.section-content .dashboard-bg .th{padding-block:calc(var(--spacing)*2)}.Dashboard>.section-content .dashboard-bg .table:not(.assoclist){margin-bottom:calc(var(--spacing)*0);border-style:var(--tw-border-style);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;border-width:0;border-radius:0}@media (hover:hover){.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr:hover{background-color:#0000}}@media not all and (min-width:48rem){.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*0);border-width:0;display:table-row}}.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*.5);border-width:0}@media not all and (min-width:48rem){.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{display:table-cell}}.Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td:first-child{padding-right:calc(var(--spacing)*2);white-space:nowrap;opacity:.7}.Dashboard>.section-content .title h3{border-style:var(--tw-border-style);padding-bottom:calc(var(--spacing)*4);color:var(--foreground);border-width:0}@media not all and (min-width:48rem){.Dashboard>.section-content .title h3{margin-inline:calc(var(--spacing)*0);padding-bottom:calc(var(--spacing)*2)}}.Dashboard>.section-content .router-status-wifi .wifi-info .devices-info .tr .td:nth-child(3){top:calc(var(--spacing)*0);width:100%;padding-inline:calc(var(--spacing)*0);position:relative}@media not all and (min-width:48rem){.Dashboard>.section-content .router-status-wifi .wifi-info .devices-info .tr .td{flex:50%}}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-spacing-x{syntax:"";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"";inherits:false;initial-value:0}@keyframes spin{to{transform:rotate(360deg)}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}} diff --git a/mihomo/.prepare.sh b/mihomo/.prepare.sh deleted file mode 100755 index e89c8386f..000000000 --- a/mihomo/.prepare.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -VERSION="$1" -CURDIR="$2" -BIN_PATH="$3" - -if [ -d "$CURDIR/.git" ]; then - config="$CURDIR/.git/config" -else - config="$(sed "s|^gitdir:\s*|$CURDIR/|;s|$|/config|" "$CURDIR/.git")" -fi -[ -n "$(sed -En '/^\[remote /{h;:top;n;/^\[/b;s,(https?://gitcode\.(com|net)),\1,;T top;H;x;s|\n\s*|: |;p;}' "$config")" ] && { - echo -e "#!/bin/sh\necho $VERSION" > "$BIN_PATH" -} -exit 0 diff --git a/mihomo/Makefile b/mihomo/Makefile index 2805b870f..729fe73ef 100644 --- a/mihomo/Makefile +++ b/mihomo/Makefile @@ -1,32 +1,30 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Copyright (C) 2024-2026 Anya Lin - include $(TOPDIR)/rules.mk PKG_NAME:=mihomo PKG_VERSION:=1.19.26 -PKG_RELEASE:=12 +PKG_RELEASE:=13 +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git +PKG_SOURCE_VERSION:=v$(PKG_VERSION) PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://codeload.github.com/metacubex/mihomo/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=skip +PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) +PKG_MIRROR_HASH:=skip -PKG_MAINTAINER:=Anya Lin -PKG_LICENSE:=GPL-2.0 -PKG_LICENSE_FILES:=LICENSE +PKG_LICENSE:=GPL3.0+ +PKG_MAINTAINER:=Joseph Mory PKG_BUILD_DEPENDS:=golang/host PKG_BUILD_PARALLEL:=1 PKG_BUILD_FLAGS:=no-mips16 -GO_PKG:=github.com/metacubex/mihomo +PKG_BUILD_VERSION:=v$(PKG_VERSION) +PKG_BUILD_TIME:=$(shell date -u -Iseconds) -PKG_BUILD_TIME:=$(shell date -u +%FT%TZ%z) -GO_PKG_LDFLAGS_X:=\ - $(GO_PKG)/constant.Version=v$(PKG_VERSION) \ - $(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME) +GO_PKG:=github.com/metacubex/mihomo +GO_PKG_LDFLAGS_X:=$(GO_PKG)/constant.Version=$(PKG_BUILD_VERSION) $(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME) GO_PKG_TAGS:=with_gvisor +GO_PKG_INSTALL_BIN_PATH:=/usr/libexec include $(INCLUDE_DIR)/package.mk include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk @@ -34,27 +32,24 @@ include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk define Package/mihomo SECTION:=net CATEGORY:=Network - TITLE:=Another Mihomo Kernel. + TITLE:=A rule based proxy in Go. URL:=https://wiki.metacubex.one - DEPENDS:=$(GO_ARCH_DEPENDS) - PROVIDES:=mihomo + DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle ALTERNATIVES:=\ - 100:/usr/bin/mihomo:/usr/libexec/mihomo-core - USERID:=mihomo=7890:mihomo=7890 + 300:/usr/bin/mihomo:/usr/libexec/mihomo +endef + +define Package/mihomo/description + Mihomo is a rule based proxy in Go. +endef + +define Package/mihomo/install + $(call GoPackage/Package/Install/Bin,$(1)) endef define Build/Prepare $(Build/Prepare/Default) - # rm unit test - rm -f $(PKG_BUILD_DIR)/rules/logic_test/logic_test.go -endef - -define Package/mihomo/install - $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR)) - $(CURDIR)/.prepare.sh $(VERSION) $(CURDIR) $(PKG_INSTALL_DIR)/usr/bin/$(PKG_NAME) - - $(INSTALL_DIR) $(1)/usr/libexec/ - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/$(PKG_NAME) $(1)/usr/libexec/mihomo-core + $(RM) -r $(PKG_BUILD_DIR)/rules/logic_test endef $(eval $(call GoBinPackage,mihomo)) diff --git a/ookla-speedtest/Makefile b/ookla-speedtest/Makefile index 6a809c1e2..9a5328d1c 100644 --- a/ookla-speedtest/Makefile +++ b/ookla-speedtest/Makefile @@ -1,65 +1,41 @@ -# -# Copyright (C) 2024 sbwml -# Copyright (c) 2025-2026 sirpdboy -# -# This is free software, licensed under the GPL-3.0 License. -# - include $(TOPDIR)/rules.mk PKG_NAME:=ookla-speedtest PKG_VERSION:=1.2.0 -PKG_RELEASE:=5 +PKG_RELEASE:=6 +PKG_MAINTAINER:=Konstantine Shevlakov -ifeq ($(ARCH),aarch64) - PKG_ARCH_SUFFIX:=aarch64 - PKG_HASH:=skip -else ifeq ($(ARCH),arm) - ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE)))) - ifeq ($(ARM_CPU_FEATURES),) - PKG_ARCH_SUFFIX:=armel - PKG_HASH:=skip - else - PKG_ARCH_SUFFIX:=armhf - PKG_HASH:=skip - endif -else ifeq ($(ARCH),i386) - PKG_ARCH_SUFFIX:=i386 - PKG_HASH:=skip -else ifeq ($(ARCH),x86_64) - PKG_ARCH_SUFFIX:=x86_64 - PKG_HASH:=skip -endif - -PKG_SOURCE:=ookla-speedtest-$(PKG_VERSION)-linux-$(PKG_ARCH_SUFFIX).tgz -PKG_SOURCE_URL:=https://install.speedtest.net/app/cli -PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)-$(PKG_ARCH_SUFFIX) - -PKG_MAINTAINER:=sbwml include $(INCLUDE_DIR)/package.mk define Package/$(PKG_NAME) - SECTION:=net - CATEGORY:=Network - SUBMENU:=Speed Test - TITLE:=Speedtest CLI by Ookla - URL:=https://www.speedtest.net/ - DEPENDS:=@(aarch64||arm||i386||x86_64) +ca-certificates - URL:=https://www.speedtest.net/ + TITLE:=Speedtest from Ookla + SECTION:=net + CATEGORY:=Network endef +ifeq ($(ARCH),x86_64) + BIN_PATH:=./files/x86_64 +endif +ifeq ($(ARCH),i386) + BIN_PATH:=./files/i386 +endif +ifeq ($(ARCH),arm) + BIN_PATH:=./files/armhf +endif +ifeq ($(ARCH),aarch64) + BIN_PATH:=./files/aarch64 +endif + define Package/$(PKG_NAME)/description - Speedtest CLI by Ookla is the official command line client - for testing internet bandwidth using speedtest.net servers. + Speedtest CLI brings the trusted technology and global server network behind Speedtest to the command line. + Built for software developers, system administrators and computer enthusiasts alike, + Speedtest CLI is the first official Linux-native Speedtest application backed by Ookla®. endef - define Build/Prepare - ( \ - pushd $(PKG_BUILD_DIR) ; \ - $(TAR) -zxf $(DL_DIR)/ookla-speedtest-$(PKG_VERSION)-linux-$(PKG_ARCH_SUFFIX).tgz -C . ; \ - popd ; \ - ) +endef + +define Build/Configure endef define Build/Compile @@ -67,7 +43,9 @@ endef define Package/$(PKG_NAME)/install $(INSTALL_DIR) $(1)/usr/bin - $(INSTALL_BIN) $(PKG_BUILD_DIR)/speedtest $(1)/usr/bin/ookla-speedtest + $(CP) $(BIN_PATH)/speedtest $(1)/usr/bin endef -$(eval $(call BuildPackage,$(PKG_NAME))) \ No newline at end of file +ifeq ($(ARCH),$(filter $(ARCH), x86_64 i386 arm aarch64)) + $(eval $(call BuildPackage,$(PKG_NAME))) +endif diff --git a/ookla-speedtest/files/aarch64/speedtest b/ookla-speedtest/files/aarch64/speedtest new file mode 100755 index 000000000..677c6e783 Binary files /dev/null and b/ookla-speedtest/files/aarch64/speedtest differ diff --git a/ookla-speedtest/files/armhf/speedtest b/ookla-speedtest/files/armhf/speedtest new file mode 100755 index 000000000..0b01c45bd Binary files /dev/null and b/ookla-speedtest/files/armhf/speedtest differ diff --git a/ookla-speedtest/files/i386/speedtest b/ookla-speedtest/files/i386/speedtest new file mode 100755 index 000000000..6a6854e53 Binary files /dev/null and b/ookla-speedtest/files/i386/speedtest differ diff --git a/ookla-speedtest/files/x86_64/speedtest b/ookla-speedtest/files/x86_64/speedtest new file mode 100755 index 000000000..44c03da45 Binary files /dev/null and b/ookla-speedtest/files/x86_64/speedtest differ diff --git a/shadowsocks-libev/Makefile b/shadowsocks-libev/Makefile index 5c5db8529..951bfa5d8 100644 --- a/shadowsocks-libev/Makefile +++ b/shadowsocks-libev/Makefile @@ -14,12 +14,14 @@ include $(TOPDIR)/rules.mk # PKG_NAME:=shadowsocks-libev PKG_VERSION:=3.3.5 -PKG_RELEASE:=3 +PKG_RELEASE:=4 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/shadowsocks/shadowsocks-libev.git PKG_SOURCE_DATE:=2025-1-20 PKG_SOURCE_VERSION:=9afa3cacf947f910be46b69fc5a7a1fdd02fd5e6 +PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) +PKG_SOURCE:=$(PKG_SOURCE_SUBDIR).tar.xz PKG_MIRROR_HASH:=skip PKG_MAINTAINER:=Yousong Zhou @@ -53,7 +55,7 @@ define Package/shadowsocks-libev-config/install $(INSTALL_DIR) $(1)/etc/config $(INSTALL_DATA) ./files/shadowsocks-libev.config $(1)/etc/config/shadowsocks-libev $(INSTALL_DIR) $(1)/etc/init.d - $(INSTALL_BIN) ./files/shadowsocks-libev.init $(1)/etc/init.d/shadowsocks-libev +# $(INSTALL_BIN) ./files/shadowsocks-libev.init $(1)/etc/init.d/shadowsocks-libev endef diff --git a/sing-box/Makefile b/sing-box/Makefile index 261c7565b..3d8877ea3 100644 --- a/sing-box/Makefile +++ b/sing-box/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=sing-box -PKG_VERSION:=1.13.12 -PKG_RELEASE:=16 +PKG_VERSION:=1.13.13 +PKG_RELEASE:=17 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)? diff --git a/trojan/Makefile b/trojan/Makefile deleted file mode 100644 index dae16c871..000000000 --- a/trojan/Makefile +++ /dev/null @@ -1,70 +0,0 @@ -# -# Copyright (C) 2018-2019 wongsyrone -# -# This is free software, licensed under the GNU General Public License v3. -# See /LICENSE for more information. -# -include $(TOPDIR)/rules.mk - -PKG_NAME:=trojan -PKG_VERSION:=1.16.0 -PKG_RELEASE:=3 - -PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://codeload.github.com/trojan-gfw/trojan/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=skip - -PKG_BUILD_PARALLEL:=1 -PKG_BUILD_DEPENDS:=openssl - -PKG_LICENSE:=GPL-3.0 -PKG_LICENSE_FILE:=LICENSE -PKG_MAINTAINER:=GreaterFire - -include $(INCLUDE_DIR)/package.mk -include $(INCLUDE_DIR)/cmake.mk -include ./boost-version.mk - -TARGET_CXXFLAGS += -Wall -Wextra -TARGET_CXXFLAGS += $(FPIC) - -# LTO -TARGET_CXXFLAGS += -flto -TARGET_LDFLAGS += -flto - -# CXX standard -TARGET_CXXFLAGS += -std=c++11 -TARGET_CXXFLAGS := $(filter-out -O%,$(TARGET_CXXFLAGS)) -O3 -TARGET_CXXFLAGS += -ffunction-sections -fdata-sections -TARGET_LDFLAGS += -Wl,--gc-sections - -CMAKE_OPTIONS += \ - -DENABLE_MYSQL=OFF \ - -DENABLE_NAT=ON \ - -DENABLE_REUSE_PORT=ON \ - -DENABLE_SSL_KEYLOG=ON \ - -DENABLE_TLS13_CIPHERSUITES=ON \ - -DFORCE_TCP_FASTOPEN=OFF \ - -DSYSTEMD_SERVICE=OFF \ - -DOPENSSL_USE_STATIC_LIBS=FALSE \ - -DBoost_DEBUG=ON \ - -DBoost_NO_BOOST_CMAKE=ON - -define Package/trojan - SECTION:=net - CATEGORY:=Network - SUBMENU:=Web Servers/Proxies - TITLE:=An unidentifiable mechanism that helps you bypass GFW - URL:=https://github.com/trojan-gfw/trojan - DEPENDS:= \ - +libpthread +libstdcpp +libopenssl \ - +boost +boost-program_options +boost-date_time \ - $(if $(filter y,$(NEED_BOOST_SYSTEM)),,+boost-system) -endef - -define Package/trojan/install - $(INSTALL_DIR) $(1)/usr/sbin - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/trojan $(1)/usr/sbin/trojan -endef - -$(eval $(call BuildPackage,trojan)) diff --git a/trojan/boost-version.mk b/trojan/boost-version.mk deleted file mode 100644 index 72beca47c..000000000 --- a/trojan/boost-version.mk +++ /dev/null @@ -1,12 +0,0 @@ -# boost-version.mk -BOOST_MAKEFILE := $(firstword $(shell find -L $(TOPDIR) -type f -path "*/boost/Makefile")) - -BOOST_PKG_VERSION := $(shell grep '^PKG_VERSION:=' $(BOOST_MAKEFILE) | head -n1 | cut -d= -f2) - -BOOST_VER_MAJOR := $(word 1,$(subst ., ,$(BOOST_PKG_VERSION))) -BOOST_VER_MINOR := $(word 2,$(subst ., ,$(BOOST_PKG_VERSION))) -BOOST_VER_PATCH := $(word 3,$(subst ., ,$(BOOST_PKG_VERSION))) - -BOOST_VERSION_CODE := $(shell echo $$(($(BOOST_VER_MAJOR)*100000 + $(BOOST_VER_MINOR)*100 + $(BOOST_VER_PATCH)))) - -NEED_BOOST_SYSTEM := $(if $(shell [ $(BOOST_VERSION_CODE) -ge 108900 ] && echo y),y,n) diff --git a/trojan/patches/001-force-openssl-version.patch b/trojan/patches/001-force-openssl-version.patch deleted file mode 100644 index 7ee8f631c..000000000 --- a/trojan/patches/001-force-openssl-version.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -43,7 +43,7 @@ if(MSVC) - add_definitions(-DBOOST_DATE_TIME_NO_LIB) - endif() - --find_package(OpenSSL 1.1.0 REQUIRED) -+find_package(OpenSSL 1.1.1 REQUIRED) - include_directories(${OPENSSL_INCLUDE_DIR}) - target_link_libraries(trojan ${OPENSSL_LIBRARIES}) - if(OPENSSL_VERSION VERSION_GREATER_EQUAL 1.1.1) diff --git a/trojan/patches/002-Fix-boost1.89-build.patch b/trojan/patches/002-Fix-boost1.89-build.patch deleted file mode 100644 index 69e236349..000000000 --- a/trojan/patches/002-Fix-boost1.89-build.patch +++ /dev/null @@ -1,16 +0,0 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -36,7 +36,12 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) - find_package(Threads REQUIRED) - target_link_libraries(trojan ${CMAKE_THREAD_LIBS_INIT}) - --find_package(Boost 1.66.0 REQUIRED COMPONENTS system program_options) -+find_package(Boost 1.66.0 REQUIRED) -+if (Boost_MAJOR_VERSION LESS_EQUAL 1 AND Boost_MINOR_VERSION LESS 89) -+ find_package(Boost 1.66.0 REQUIRED COMPONENTS system program_options) -+else() -+ find_package(Boost 1.66.0 REQUIRED COMPONENTS program_options) -+endif() - include_directories(${Boost_INCLUDE_DIR}) - target_link_libraries(trojan ${Boost_LIBRARIES}) - if(MSVC)