🗽 Sync 2026-05-20 20:54:52

This commit is contained in:
github-actions[bot] committed 2026-05-20 20:54:52 +08:00
1 parent 4346860627
commit 0c34888fa7
17 files changed
+202 -154

No files matched your search

+38 -11
View File
@@ -1,5 +1,5 @@
#
# Copyright (C) 2015-2016 OpenWrt.org
# Copyright (C) 2015-2024 OpenWrt.org
# Copyright (C) 2020 jjm2473@gmail.com
#
# This is free software, licensed under the GNU General Public License v3.
@@ -10,9 +10,17 @@ include $(TOPDIR)/rules.mk
PKG_ARCH_DDNSTO:=$(ARCH)
PKG_NAME:=ddnsto
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
PKG_SOURCE_DATE:=3.0.4
PKG_RELEASE:=1
# use PKG_SOURCE_DATE instead of PKG_VERSION for compatible
PKG_SOURCE_DATE:=4.2.0
PKG_RELEASE:=2
# 构建类型:standard 或 lite
DDNSTO_TYPE?=standard
# 调试信息
$(info DDNSTO_TYPE is set to: $(DDNSTO_TYPE))
# 架构名称映射
ARCH_HEXCODE:=
ifeq ($(ARCH),x86_64)
ARCH_HEXCODE=8664
@@ -24,29 +32,49 @@ else ifeq ($(ARCH),mipsel)
ARCH_HEXCODE=1b0c
endif
PKG_SOURCE_VERSION:=$(ARCH_HEXCODE)
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_SOURCE_DATE).tar.gz
PKG_SOURCE_URL:=https://dl.istoreos.com/binary/ddnsto/
PKG_HASH:=skip
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_SOURCE_DATE)
# 根据类型选择下载地址和文件名
ifeq ($(DDNSTO_TYPE),lite)
PKG_SOURCE:=$(PKG_NAME)-binary-lite-$(PKG_SOURCE_DATE).tar.gz
PKG_SOURCE_URL:=https://github.com/linkease/ddnsto-openwrt-package/raw/refs/heads/main/
PKG_HASH:=skip
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-lite-$(PKG_SOURCE_DATE)
else
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_SOURCE_DATE).tar.gz
PKG_SOURCE_URL:=https://github.com/linkease/ddnsto-openwrt-package/raw/refs/heads/main/
PKG_HASH:=skip
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_SOURCE_DATE)
endif
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
# 禁用 strip,保持二进制文件原样
STRIP:=true
RSTRIP:=:
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
SUBMENU:=Web Servers/Proxies
TITLE:=DDNS.to - the reverse proxy
TITLE:=DDNS.to - the reverse proxy ($(DDNSTO_TYPE))
DEPENDS:=
PKGARCH:=all
URL:=https://www.ddnsto.com/
endef
define Package/$(PKG_NAME)/description
DDNS.to is a reverse proxy
DDNS.to is a reverse proxy service that allows you to access
your devices from anywhere without public IP or port forwarding.
Build type: $(DDNSTO_TYPE)
Features:
- Multi-device index support
- WebDAV file sharing
- Intranet penetration
endef
define Package/$(PKG_NAME)/conffiles
@@ -70,7 +98,6 @@ endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d $(1)/etc/uci-defaults
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ddnsto.$(PKG_ARCH_DDNSTO) $(1)/usr/sbin/ddnstod
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ddwebdav.$(PKG_ARCH_DDNSTO) $(1)/usr/sbin/ddwebdav
$(INSTALL_CONF) ./files/ddnsto.config $(1)/etc/config/ddnsto
$(INSTALL_BIN) ./files/ddnsto.init $(1)/etc/init.d/ddnsto
$(INSTALL_BIN) ./files/ddnsto.uci-default $(1)/etc/uci-defaults/ddnsto
-49
View File
@@ -1,49 +0,0 @@
#!/bin/sh
DEVICE_IDX=0
LOG_LEVEL=2
while getopts u:x:l: flag
do
case "${flag}" in
u) TOKEN=${OPTARG};;
x) DEVICE_IDX=${OPTARG};;
l) LOG_LEVEL=${OPTARG};;
esac
done
if [ -z "${TOKEN}" ]; then
logger "ddnsto: the token is empty, get token from https://www.ddnsto.com/ "
exit 2
fi
echo "ddnsto version device_id is is:"
/usr/sbin/ddnsto -u ${TOKEN} -w
_term() {
logger "ddnsto: SIGTERM"
killall ddnsto 2>/dev/null
killall ddwebdav 2>/dev/null
rm -f /tmp/.ddnsto.pid
rm -f /tmp/.ddnsto.status
rm -f /tmp/.ddnsto.up
exit
}
trap "_term;" SIGTERM
while true ; do
if ! pidof "ddnsto" > /dev/null ; then
logger "ddnsto try running"
/usr/sbin/ddnsto -u ${TOKEN} -x ${DEVICE_IDX} &
PID=$!
wait $PID
RET=$?
logger "ddnsto EXIT CODE: ${RET}"
if [ "${RET}" == "100" ]; then
logger "ddnsto token error, please set a correct token from https://www.ddnsto.com/ "
exit 100
fi
fi
sleep 20
done
+8 -2
View File
@@ -1,5 +1,11 @@
config ddnsto
option enabled '0'
option feat_port '3033'
option feat_enabled '0'
# option token ''
option index '0'
option logger '0'
option feat_enabled '0'
option feat_port '3033'
# option feat_username ''
# option feat_password ''
# option feat_disk_path_selected ''
# option address ''
Executable → Regular
+30 -26
View File
@@ -4,45 +4,49 @@ START=99
USE_PROCD=1
get_config() {
config_get_bool enabled $1 enabled 1
config_get_bool logger $1 logger 0
config_get token $1 token
config_get index $1 index 0
config_get_bool feat_enabled $1 feat_enabled 1
config_get_bool enabled $1 enabled 0
config_get_bool logger $1 logger 0
config_get token $1 token
config_get index $1 index 0
config_get_bool feat_enabled $1 feat_enabled 0
}
ddnsto_prepare() {
killall ddnstod 2>/dev/null
killall ddwebdav 2>/dev/null
killall ddnstod 2>/dev/null
}
stop_service() {
ddnsto_prepare
ddnsto_prepare
}
start_service() {
ddnsto_prepare
config_load ddnsto
config_foreach get_config ddnsto
if [ $enabled != 1 ]; then
return 1
fi
ddnsto_prepare
config_load ddnsto
config_foreach get_config ddnsto
if [ "$enabled" != "1" ]; then
logger -t ddnsto -p daemon.info "ddnsto service disabled"
return 1
fi
if [ -z "$token" ]; then
logger -t ddnsto -p warn "token not set"
return 1
fi
procd_open_instance
procd_set_param command /usr/sbin/ddnstod -u "$token" -x $index -F
[ "$logger" == 1 ] && procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
if [ -z "$token" ]; then
logger -t ddnsto -p daemon.warn "token not set"
return 1
fi
procd_open_instance
procd_set_param command /usr/sbin/ddnstod -u "$token" -x "$index" -F
[ "$logger" == "1" ] && procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
}
service_triggers() {
procd_add_reload_trigger "ddnsto"
procd_add_reload_trigger "ddnsto"
}
reload_service() {
restart
stop
sleep 1
start
}
+1 -1
View File
@@ -17,4 +17,4 @@ EOF
/etc/init.d/ddnsto enable
exit 0
exit 0
+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=ipv6-neigh
PKG_VERSION:=0.1.0
PKG_RELEASE:=11
PKG_RELEASE:=12
PKG_BUILD_DEPENDS:=rust/host
PKG_BUILD_PARALLEL:=1
+50 -35
View File
@@ -30,7 +30,7 @@ mod types;
use filter::{if_ipv4_in_private_subnet, if_ipv6_in_private_subnet, ipv6_in_prefix, ipv6_passes_active_prefix, is_gua_ipv6};
use iface::{get_active_lan_prefixes, register_router_addresses, wait_for_dns_server};
use probe::{probe_gua_keepalive, probe_registered_neighbours, prune_gua_keepalive, send_icmpv4_echo, send_icmpv6_echo};
use reconcile::{do_delete_dns, process_new_neigh, prune_ula_for_mac, reconcile_dns};
use reconcile::{do_delete_dns, process_new_neigh, prune_ula_for_host, reconcile_dns};
use types::{
DEFAULT_TTL, RTNLGRP_NEIGH, GuaKeepaliveEntry, Neigh, RegisteredEntry, nl_mgrp,
};
@@ -253,16 +253,17 @@ async fn main() -> Result<(), ()> {
}
}
let key = (neigh.mac.clone(), inet_to_string(&neigh.inet));
let ip_str = inet_to_string(&neigh.inet);
match neigh.state {
NeighbourState::Reachable => {
// Confirmed online — register in DNS immediately.
if !registered.contains_key(&key) {
if let Some(hostname) = leases.get(&neigh.mac) {
if let Some(hostname) = leases.get(&neigh.mac) {
let key = (hostname.clone(), ip_str.clone());
if !registered.contains_key(&key) {
// Enforce per-host ULA limit before adding a new one.
if let NeighbourAddress::Inet6(addr) = &neigh.inet {
if if_ipv6_in_private_subnet(addr) {
prune_ula_for_mac(&neigh.mac, max_ula_per_host, &mut registered, &updater).await;
prune_ula_for_host(hostname, max_ula_per_host, &mut registered, &updater).await;
}
}
let result = match &neigh.inet {
@@ -281,9 +282,9 @@ async fn main() -> Result<(), ()> {
}
Err(e) => error!("DNS update failed for {}: {}", hostname, e),
}
} else {
debug!("no lease for mac {}, skipping DNS update", neigh.mac);
}
} else {
debug!("no lease for mac {}, skipping DNS update", neigh.mac);
}
}
NeighbourState::Stale | NeighbourState::Delay | NeighbourState::Probe => {
@@ -409,29 +410,39 @@ async fn main() -> Result<(), ()> {
}
}
let key = (neigh.mac.clone(), inet_to_string(&neigh.inet));
let ip_str = inet_to_string(&neigh.inet);
if is_failed_state(neigh.state) {
// Neighbour confirmed unreachable — remove DNS record
if let Some(entry) = registered.remove(&key) {
debug!("Neighbour failed: {:?}", neigh);
if !do_delete_dns(&entry.hostname, &neigh.inet, &updater).await {
// DNS delete failed, put back so we retry
registered.insert(key, entry);
// Neighbour confirmed unreachable — remove DNS record.
// Resolve key by hostname (handles MAC changes: new MAC in leases
// can now evict entries registered under an old MAC).
// Fall back to scanning registered by IP if MAC is no longer in leases.
let key_opt: Option<(String, String)> = leases
.get(&neigh.mac)
.map(|h| (h.clone(), ip_str.clone()))
.or_else(|| registered.keys().find(|(_, ip)| ip == &ip_str).cloned());
if let Some(key) = key_opt {
if let Some(entry) = registered.remove(&key) {
debug!("Neighbour failed: {:?}", neigh);
if !do_delete_dns(&entry.hostname, &neigh.inet, &updater).await {
// DNS delete failed, put back so we retry
registered.insert(key, entry);
}
}
}
} else if neigh.state == NeighbourState::Reachable {
if let Some(entry) = registered.get_mut(&key) {
// Already registered, just update timestamp and ifindex
entry.last_confirmed = Instant::now();
entry.ifindex = neigh.ifindex;
} else {
// New reachable neighbour — register in DNS
debug!("New neighbour: {:?}", neigh);
if let Some(hostname) = leases.get(&neigh.mac) {
if let Some(hostname) = leases.get(&neigh.mac) {
let key = (hostname.clone(), ip_str.clone());
if let Some(entry) = registered.get_mut(&key) {
// Already registered, just update timestamp and ifindex
entry.last_confirmed = Instant::now();
entry.ifindex = neigh.ifindex;
} else {
// New reachable neighbour — register in DNS
debug!("New neighbour: {:?}", neigh);
// Enforce per-host ULA limit before adding.
if let NeighbourAddress::Inet6(addr) = &neigh.inet {
if if_ipv6_in_private_subnet(addr) {
prune_ula_for_mac(&neigh.mac, max_ula_per_host, &mut registered, &updater).await;
prune_ula_for_host(hostname, max_ula_per_host, &mut registered, &updater).await;
}
}
if process_new_neigh(&neigh, &updater, &leases, private_subnet_v6).await {
@@ -441,17 +452,20 @@ async fn main() -> Result<(), ()> {
ifindex: neigh.ifindex,
});
}
} else {
debug!("no lease for mac {}, skipping DNS update", neigh.mac);
}
} else {
debug!("no lease for mac {}, skipping DNS update", neigh.mac);
}
} else if matches!(neigh.state, NeighbourState::Stale | NeighbourState::Delay | NeighbourState::Probe) {
// Uncertain state: update ifindex only so the next probe uses
// the right interface, but do NOT refresh last_confirmed —
// STALE/DELAY/PROBE is not confirmed reachable and refreshing
// the timestamp would suppress the periodic probe.
if let Some(entry) = registered.get_mut(&key) {
entry.ifindex = neigh.ifindex;
if let Some(hostname) = leases.get(&neigh.mac) {
let key = (hostname.clone(), ip_str.clone());
if let Some(entry) = registered.get_mut(&key) {
entry.ifindex = neigh.ifindex;
}
}
}
}
@@ -474,15 +488,16 @@ async fn main() -> Result<(), ()> {
}
}
}
let key = (neigh.mac.clone(), inet_to_string(&neigh.inet));
let stored_hostname = registered.remove(&key).map(|e| e.hostname);
let ip_str = inet_to_string(&neigh.inet);
// Resolve key by hostname; fall back to scanning by IP if MAC gone from leases.
let key_opt: Option<(String, String)> = leases
.get(&neigh.mac)
.map(|h| (h.clone(), ip_str.clone()))
.or_else(|| registered.keys().find(|(_, ip)| ip == &ip_str).cloned());
debug!("Del neighbour: {:?}", neigh);
// Use stored hostname if available, fall back to leases for records
// that survived a program restart (not in registered).
let hostname_opt = stored_hostname
.or_else(|| leases.get(&neigh.mac).cloned());
if let Some(ref hostname) = hostname_opt {
do_delete_dns(hostname, &neigh.inet, &updater).await;
if let Some(key) = key_opt {
registered.remove(&key);
do_delete_dns(&key.0, &neigh.inet, &updater).await;
}
}
_ => {}
+10 -10
View File
@@ -67,20 +67,20 @@ pub(crate) async fn do_delete_dns(
}
}
/// Enforce the per-host ULA AAAA limit. If `mac` already has `max` or more ULA AAAA
/// Enforce the per-host ULA AAAA limit. If `host` already has `max` or more ULA AAAA
/// records in `registered`, remove the oldest (by `last_confirmed`) and delete from DNS.
/// Returns the number of existing ULA AAAA records for this MAC (after pruning).
pub(crate) async fn prune_ula_for_mac(
mac: &str,
/// Returns the number of existing ULA AAAA records for this host (after pruning).
pub(crate) async fn prune_ula_for_host(
host: &str,
max: usize,
registered: &mut HashMap<(String, String), RegisteredEntry>,
updater: &DnsUpdater,
) -> usize {
// Collect existing ULA AAAA keys for this MAC.
// Collect existing ULA AAAA keys for this host.
let mut ula_keys: Vec<((String, String), Instant)> = registered
.iter()
.filter(|((m, ip_str), _)| {
m == mac && ip_str.parse::<Ipv6Addr>().map_or(false, |a| if_ipv6_in_private_subnet(&a))
.filter(|((h, ip_str), _)| {
h == host && ip_str.parse::<Ipv6Addr>().map_or(false, |a| if_ipv6_in_private_subnet(&a))
})
.map(|(k, e)| (k.clone(), e.last_confirmed))
.collect();
@@ -99,15 +99,15 @@ pub(crate) async fn prune_ula_for_mac(
let addr: Ipv6Addr = key.1.parse().unwrap();
let inet = NeighbourAddress::Inet6(addr);
do_delete_dns(&entry.hostname, &inet, updater).await;
info!("ULA pruning: removed oldest {} -> {} for mac {}", entry.hostname, key.1, mac);
info!("ULA pruning: removed oldest {} -> {} for host {}", entry.hostname, key.1, host);
}
}
// Return count after pruning.
registered
.iter()
.filter(|((m, ip_str), _)| {
m == mac && ip_str.parse::<Ipv6Addr>().map_or(false, |a| if_ipv6_in_private_subnet(&a))
.filter(|((h, ip_str), _)| {
h == host && ip_str.parse::<Ipv6Addr>().map_or(false, |a| if_ipv6_in_private_subnet(&a))
})
.count()
}
+2 -2
View File
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for ddnsto
LUCI_DEPENDS:=+ddnsto +block-mount
LUCI_PKGARCH:=all
PKG_VERSION:=3.2.0-r5
PKG_RELEASE:=1
PKG_VERSION:=4.0.3-r1
PKG_RELEASE:=2
include $(TOPDIR)/feeds/luci/luci.mk
+39 -4
View File
@@ -1,9 +1,9 @@
--wulishui <wulishui@gmail.com> ,20200911
--jjm2473 <jjm2473@gmail.com> ,20210127
local m, s
local m, s, o
m = Map("ddnsto", translate("DDNS.to"), translate("DDNS.to is a reverse proxy.")
m = Map("ddnsto", translate("DDNS.to"), translate("DDNS.to is a reverse proxy service.")
.. " <a href=\"https://www.ddnsto.com/\" onclick=\"void(0)\" target=\"_blank\">"
.. translate("Official Website")
.. "</a>")
@@ -18,6 +18,41 @@ s:option(Flag, "enabled", translate("Enable")).rmempty=false
s:option(Value, "token", translate("Token")).rmempty=false
o = s:option(Value, "index", translate("Device Index"))
o.default = "0"
o.datatype = "uinteger"
o.rmempty = false
o = s:option(Flag, "logger", translate("Enable Logging"))
o.default = "0"
o.rmempty = false
-- WebDAV/File Sharing settings
s2=m:section(TypedSection, "ddnsto", translate("WebDAV File Sharing"))
s2.addremove=false
s2.anonymous=true
o = s2:option(Flag, "feat_enabled", translate("Enable WebDAV"))
o.default = "0"
o.rmempty = false
o = s2:option(Value, "feat_port", translate("WebDAV Port"))
o.default = "3033"
o.datatype = "port"
o.rmempty = false
o:depends("feat_enabled", "1")
o = s2:option(Value, "feat_username", translate("Username"))
o.rmempty = false
o:depends("feat_enabled", "1")
o = s2:option(Value, "feat_password", translate("Password"))
o.password = true
o.rmempty = false
o:depends("feat_enabled", "1")
o = s2:option(Value, "feat_disk_path_selected", translate("Shared Path"))
o.rmempty = false
o:depends("feat_enabled", "1")
return m
@@ -97,6 +97,6 @@ Error generating stack: `+o.message+`
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const vf=[["rect",{width:"14",height:"20",x:"5",y:"2",rx:"2",ry:"2",key:"1yt0o3"}],["path",{d:"M12 18h.01",key:"mhygvu"}]],Vu=Gt("smartphone",vf);function yf(){return m.jsx("div",{className:"bg-white rounded-lg border border-slate-200 p-6 mb-4",children:m.jsx("div",{className:"flex items-start",children:m.jsxs("div",{className:"flex-1",children:[m.jsxs("div",{className:"flex items-center gap-3 mb-3",children:[m.jsx("div",{className:"w-10 h-10 rounded-full bg-blue-50 flex items-center justify-center",children:m.jsx(Bu,{className:"w-6 h-6 text-blue-600"})}),m.jsx("h2",{className:"text-slate-800",children:"DDNSTO 远程访问"})]}),m.jsx("p",{className:"text-slate-600 text-sm mb-2",children:"DDNSTO 远程访问,不需要公网 IP、不需要开放端口。只需一个链接,即可从任何地方安全访问您的 NAS、路由器和桌面。"}),m.jsxs("a",{href:"https://www.ddnsto.com",target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 text-sm text-blue-600 hover:underline",children:["了解更多",m.jsx(Al,{className:"w-3 h-3"})]})]})})})}function wf({isRunning:y,isConfigured:E,hostname:d,address:U,deviceId:T,version:L,featEnabled:B,tunnelOk:G,onRefreshStatus:A}){const[Q,Z]=C.useState(!1),W={label:y?"运行中":"已停止",bgColor:y?"bg-green-100":"bg-slate-200",textColor:y?"text-green-700":"text-slate-600"},H=U&&U.trim().length>0?U:"未配置",q=Q,ae=G===!0,V=q?"检查中...":G===!0?"正常":G===!1?"无法连接服务器":"未知",Y=B==="1"||B===!0?"已开启":"未开启",we=T||"-",ze=async()=>{if(A){Z(!0);try{await A()}finally{Z(!1)}}};return m.jsxs("div",{className:"bg-white rounded-lg border border-slate-200 p-6 mb-4",children:[m.jsx("h3",{className:"text-slate-800 mb-4",children:"运行状态"}),m.jsxs("div",{className:"flex items-center justify-between pb-6 border-b border-slate-100",children:[m.jsxs("div",{className:"flex items-center gap-4",children:[m.jsx("span",{className:`px-3 py-1 rounded-full text-sm ${W.bgColor} ${W.textColor}`,children:W.label}),E&&m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-sm text-slate-500",children:"远程访问域名:"}),U&&U.trim().length>0?m.jsxs("a",{href:H,target:"_blank",rel:"noopener noreferrer",className:"text-sm text-blue-600 hover:underline flex items-center gap-1",children:[H,m.jsx(Al,{className:"w-3 h-3"})]}):m.jsx("span",{className:"text-sm text-slate-500",children:H})]})]}),m.jsx("div",{className:"flex items-center gap-2",children:m.jsxs("button",{onClick:ze,className:"px-3 py-2 rounded-md text-blue-600 text-sm hover:bg-blue-50 transition-colors flex items-center gap-1",children:[m.jsx(hf,{className:"w-4 h-4"}),"刷新状态"]})})]}),m.jsx("div",{className:"mt-6",children:m.jsxs("div",{className:"flex items-center gap-2 text-sm text-slate-700",children:[m.jsx(Bu,{className:"w-4 h-4 text-slate-400"}),q&&m.jsx("div",{className:"w-4 h-4 rounded-full border-2 border-slate-300 border-t-blue-600 animate-spin"}),m.jsxs("h4",{className:"text-sm text-slate-700",children:["服务器连接:",ae?m.jsx("span",{className:"text-green-600",children:V}):m.jsx("span",{children:V}),"| 设备 ID:",we," | 拓展功能:",Y,L?` | 版本:${L}`:""]})]})})]})}function Qu({checked:y,onChange:E,label:d,id:U,disabled:T,containerClassName:L,...B}){return m.jsxs("div",{className:`ddnsto-toggle-container ${L||""}`,children:[d&&m.jsx("label",{htmlFor:U,className:"text-sm text-slate-700 mr-3 whitespace-nowrap",children:d}),m.jsxs("label",{className:"ddnsto-toggle-switch","aria-label":d||"toggle",children:[m.jsx("input",{id:U,type:"checkbox",checked:y,disabled:T,onChange:G=>E(G.target.checked),...B}),m.jsx("span",{className:"ddnsto-toggle-slider","aria-hidden":!0})]})]})}function kf({onSave:y,isInTab:E,token:d,enabled:U,advancedConfig:T,onRegisterSave:L,onTokenChange:B,onEnabledChange:G}){const[A,Q]=C.useState(d||""),[Z,W]=C.useState(U);C.useEffect(()=>{Q(d||"")},[d]),C.useEffect(()=>{W(U)},[T,U]);const H=()=>{A.trim()&&y(A,Z?"1":"0",T)};return C.useEffect(()=>{L&&L(()=>H())},[L,A,Z,T]),m.jsxs("div",{className:E?"":"bg-white rounded-lg border border-slate-200 p-6 mb-4",children:[!E&&m.jsxs(m.Fragment,{children:[m.jsx("h3",{className:"text-slate-800 mb-2",children:"手动配置"}),m.jsx("p",{className:"text-xs text-slate-400 mb-6",children:"如果您已经在 DDNSTO 控制台获取了令牌,可以直接在此填写并启动插件。"})]}),m.jsxs("div",{className:"space-y-5",children:[m.jsx("div",{className:"pb-4",children:m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("h4",{className:"text-sm text-slate-700",children:"启用 DDNSTO"}),m.jsx(Qu,{checked:Z,onChange:q=>{W(q),G?.(q)},"aria-label":"启用 DDNSTO",containerClassName:"ml-6"})]})}),m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[m.jsxs("label",{className:"text-sm text-slate-700",children:["用户令牌(Token) ",m.jsx("span",{className:"text-red-500",children:"*"})]}),m.jsLine truncated
*/const vf=[["rect",{width:"14",height:"20",x:"5",y:"2",rx:"2",ry:"2",key:"1yt0o3"}],["path",{d:"M12 18h.01",key:"mhygvu"}]],Vu=Gt("smartphone",vf);function yf(){return m.jsx("div",{className:"bg-white rounded-lg border border-slate-200 p-6 mb-4",children:m.jsx("div",{className:"flex items-start",children:m.jsxs("div",{className:"flex-1",children:[m.jsxs("div",{className:"flex items-center gap-3 mb-3",children:[m.jsx("div",{className:"w-10 h-10 rounded-full bg-blue-50 flex items-center justify-center",children:m.jsx(Bu,{className:"w-6 h-6 text-blue-600"})}),m.jsx("h2",{className:"text-slate-800",children:"DDNSTO 远程访问"})]}),m.jsx("p",{className:"text-slate-600 text-sm mb-2",children:"DDNSTO 远程访问,不需要公网 IP、不需要开放端口。只需一个链接,即可从任何地方安全访问您的 NAS、路由器和桌面。"}),m.jsxs("a",{href:"https://www.ddnsto.com",target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 text-sm text-blue-600 hover:underline",children:["了解更多",m.jsx(Al,{className:"w-3 h-3"})]})]})})})}function wf({isRunning:y,isConfigured:E,hostname:d,address:U,deviceId:T,version:L,featEnabled:B,tunnelOk:G,onRefreshStatus:A}){const[Q,Z]=C.useState(!1),W={label:y?"运行中":"已停止",bgColor:y?"bg-green-100":"bg-slate-200",textColor:y?"text-green-700":"text-slate-600"},H=U&&U.trim().length>0?U:"未配置",q=Q,ae=G===!0,V=q?"检查中...":G===!0?"正常":G===!1?"无法连接服务器":"未知",Y=B==="1"||B===!0?"已开启":"未开启",we=T||"-",ze=async()=>{if(A){Z(!0);try{await A()}finally{Z(!1)}}};return m.jsxs("div",{className:"bg-white rounded-lg border border-slate-200 p-6 mb-4",children:[m.jsx("h3",{className:"text-slate-800 mb-4",children:"运行状态"}),m.jsxs("div",{className:"flex items-center justify-between pb-6 border-b border-slate-100",children:[m.jsxs("div",{className:"flex items-center gap-4",children:[m.jsx("span",{className:`px-3 py-1 rounded-full text-sm ${W.bgColor} ${W.textColor}`,children:W.label}),E&&m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-sm text-slate-500",children:"远程访问域名:"}),U&&U.trim().length>0?m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsxs("a",{href:H,target:"_blank",rel:"noopener noreferrer",className:"text-sm text-blue-600 hover:underline flex items-center gap-1",children:[H,m.jsx(Al,{className:"w-3 h-3"})]}),m.jsx("span",{className:"text-xs text-amber-600",title:"如域名已失效,请重新运行快速向导",children:"(如失效请重新运行快速向导)"})]}):m.jsx("span",{className:"text-sm text-slate-500",children:H})]})]}),m.jsx("div",{className:"flex items-center gap-2",children:m.jsxs("button",{onClick:ze,className:"px-3 py-2 rounded-md text-blue-600 text-sm hover:bg-blue-50 transition-colors flex items-center gap-1",children:[m.jsx(hf,{className:"w-4 h-4"}),"刷新状态"]})})]}),m.jsx("div",{className:"mt-6",children:m.jsxs("div",{className:"flex items-center gap-2 text-sm text-slate-700",children:[m.jsx(Bu,{className:"w-4 h-4 text-slate-400"}),q&&m.jsx("div",{className:"w-4 h-4 rounded-full border-2 border-slate-300 border-t-blue-600 animate-spin"}),m.jsxs("h4",{className:"text-sm text-slate-700",children:["服务器连接:",ae?m.jsx("span",{className:"text-green-600",children:V}):m.jsx("span",{children:V}),"| 设备 ID:",we," | 拓展功能:",Y,L?` | 版本:${L}`:""]})]})})]})}function Qu({checked:y,onChange:E,label:d,id:U,disabled:T,containerClassName:L,...B}){return m.jsxs("div",{className:`ddnsto-toggle-container ${L||""}`,children:[d&&m.jsx("label",{htmlFor:U,className:"text-sm text-slate-700 mr-3 whitespace-nowrap",children:d}),m.jsxs("label",{className:"ddnsto-toggle-switch","aria-label":d||"toggle",children:[m.jsx("input",{id:U,type:"checkbox",checked:y,disabled:T,onChange:G=>E(G.target.checked),...B}),m.jsx("span",{className:"ddnsto-toggle-slider","aria-hidden":!0})]})]})}function kf({onSave:y,isInTab:E,token:d,enabled:U,advancedConfig:T,onRegisterSave:L,onTokenChange:B,onEnabledChange:G}){const[A,Q]=C.useState(d||""),[Z,W]=C.useState(U);C.useEffect(()=>{Q(d||"")},[d]),C.useEffect(()=>{W(U)},[T,U]);const H=()=>{A.trim()&&y(A,Z?"1":"0",T)};return C.useEffect(()=>{L&&L(()=>H())},[L,A,Z,T]),m.jsxs("div",{className:E?"":"bg-white rounded-lg border border-slate-200 p-6 mb-4",children:[!E&&m.jsxs(m.Fragment,{children:[m.jsx("h3",{className:"text-slate-800 mb-2",children:"手动配置"}),m.jsx("p",{className:"text-xs text-slate-400 mb-6",children:"如果您已经在 DDNSTO 控制台获取了令牌,可以直接在此填写并启动插件。"})]}),m.jsxs("div",{className:"space-y-5",children:[m.jsx("div",{className:"pb-4",children:m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("h4",{className:"text-sm text-slate-700",children:"启用 DDNSTO"}),m.jsx(Qu,{checked:Z,onChange:q=>{W(q),G?.(q)},"aria-label":"启用 DDNSTO",containerClassName:"ml-6"})]})}),m.jsxs("div",Line truncated
${jf}
${zf}`;if(Mf&&gt.attachShadow){const E=gt.shadowRoot||gt.attachShadow({mode:"open"});if(!E.querySelector("style[data-ddnsto-style]")){const d=document.createElement("style");d.setAttribute("data-ddnsto-style","true"),d.textContent=y,E.appendChild(d)}Ui=E,bl(gt,Ul),bl(gt.parentElement,Ul)}else{if(!document.head.querySelector("style[data-ddnsto-style]")){const E=document.createElement("style");E.setAttribute("data-ddnsto-style","true"),E.textContent=y,document.head.appendChild(E)}bl(gt,Ul),bl(gt.parentElement,Ul)}Ui&&qd.createRoot(Ui).render(m.jsx(Kd.StrictMode,{children:m.jsx(Ef,{config:Lf})}))}
+2 -2
View File
@@ -7,8 +7,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.5.11
PKG_RELEASE:=129
PKG_VERSION:=26.5.20
PKG_RELEASE:=130
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -78,6 +78,7 @@ if not arg_select_proto:find("_") then
load_normal_options = true
end
local netdev_list = api.get_network_devices()
local node_list = api.get_node_list()
local fallback_list = {}
local is_balancer = nil
@@ -273,8 +274,10 @@ end -- [[ 负载均衡 End ]]
if load_iface_options then -- [[ 自定义接口 Start ]]
o = s:option(Value, _n("iface"), translate("Interface"))
o.default = "eth1"
o:depends({ [_n("protocol")] = "_iface" })
for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
end -- [[ 自定义接口 End ]]
@@ -818,8 +821,7 @@ if not load_shunt_options then
o3 = s:option(Value, _n("outbound_iface"), translate("Outbound Interface"))
o3:depends({ [_n("chain_proxy")] = "3" })
o3:value("", translate("All"))
local iface = api.get_network_devices()
for _, d in ipairs(iface) do
for _, d in ipairs(netdev_list) do
o3:value(d.name, d.label)
end
@@ -91,6 +91,7 @@ if not arg_select_proto:find("_") then
load_normal_options = true
end
local netdev_list = api.get_network_devices()
local node_list = api.get_node_list()
if load_urltest_options then -- [[ URLTest Start ]]
@@ -196,8 +197,10 @@ end -- [[ URLTest End ]]
if load_iface_options then -- [[ 自定义接口 Start ]]
o = s:option(Value, _n("iface"), translate("Interface"))
o.default = "eth1"
o:depends({ [_n("protocol")] = "_iface" })
for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
end
@@ -836,8 +839,7 @@ if not load_shunt_options then
o3 = s:option(Value, _n("outbound_iface"), translate("Outbound Interface"))
o3:depends({ [_n("chain_proxy")] = "3" })
o3:value("", translate("All"))
local iface = api.get_network_devices()
for _, d in ipairs(iface) do
for _, d in ipairs(netdev_list) do
o3:value(d.name, d.label)
end
@@ -483,8 +483,11 @@ o:depends({ [_n("outbound_node")] = "_socks"})
o:depends({ [_n("outbound_node")] = "_http"})
o = s:option(Value, _n("outbound_node_iface"), translate("Interface"))
o.default = "eth1"
o:depends({ [_n("outbound_node")] = "_iface"})
local netdev_list = api.get_network_devices()
for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
o.rows = 10
@@ -461,8 +461,11 @@ o:depends({ [_n("outbound_node")] = "_socks" })
o:depends({ [_n("outbound_node")] = "_http" })
o = s:option(Value, _n("outbound_node_iface"), translate("Interface"))
o.default = "eth1"
o:depends({ [_n("outbound_node")] = "_iface" })
local netdev_list = api.get_network_devices()
for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
o.rows = 10
+2 -2
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=teleproxy
PKG_VERSION:=4.12.2
PKG_RELEASE:=9
PKG_RELEASE:=10
PKG_MAINTAINER:=Kosntantine Shevlakov <shevlakov@132lan.ru>
PKG_LICENSE:=GPLv2
@@ -10,7 +10,7 @@ PKG_LICENSE_FILES:=LICENSE
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/teleproxy/teleproxy.git
PKG_SOURCE_VERSION:=12a822ef794bab357772348d698dfef3a13487de
PKG_SOURCE_VERSION:=2f26609daf78765e09fcea8b0d906d9e2532faf9
PKG_SOURCE_SUBDIR:=$(PKG_NAME)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz