mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 16:44:23 +08:00
🔥 Sync 2026-10-24 23:19:23
This commit is contained in:
1 parent
9886197604
commit
37e1475e87
59 files changed
+5167
-1483
No files matched your search
+4
-4
@@ -1,17 +1,17 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=clashoo
|
PKG_NAME:=clashoo
|
||||||
PKG_SHORT_SHA:=3f27ef3
|
PKG_SHORT_SHA:=91e85ee
|
||||||
PKG_COMMIT_DATE:=2026.10.21
|
PKG_COMMIT_DATE:=2026.10.24
|
||||||
|
|
||||||
PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA)
|
PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA)
|
||||||
PKG_RELEASE:=16
|
PKG_RELEASE:=17
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA).tar.gz
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
|
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
|
||||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src
|
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src
|
||||||
PKG_SOURCE_VERSION:=c8979247b7baf46202891be29e174e3f54173cee
|
PKG_SOURCE_VERSION:=413a02e9b55b94dc3892616f43822e191516e493
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA)
|
PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ PKG_SOURCE_URL:=https://github.com/ysurac/glorytun.git
|
|||||||
PKG_SOURCE_VERSION:=203c5f2223d7d5146d659b51159b2f02c2480d15
|
PKG_SOURCE_VERSION:=203c5f2223d7d5146d659b51159b2f02c2480d15
|
||||||
PKG_NAME:=glorytun-udp
|
PKG_NAME:=glorytun-udp
|
||||||
PKG_VERSION:=0.3.4
|
PKG_VERSION:=0.3.4
|
||||||
PKG_RELEASE:=5
|
PKG_RELEASE:=6
|
||||||
PKG_LICENSE:=BSD-2-Clause
|
PKG_LICENSE:=BSD-2-Clause
|
||||||
PKG_LICENSE_FILES:=LICENSE
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
|
|
||||||
|
|||||||
+12
-7
@@ -31,12 +31,14 @@ validate_section() {
|
|||||||
'kxtimeout:string:7d' \
|
'kxtimeout:string:7d' \
|
||||||
'timetolerance:string:10m' \
|
'timetolerance:string:10m' \
|
||||||
'keepalive:string:25s' \
|
'keepalive:string:25s' \
|
||||||
|
'localip:string' \
|
||||||
|
'remoteip:string' \
|
||||||
'dev:string'
|
'dev:string'
|
||||||
}
|
}
|
||||||
|
|
||||||
start_instance() {
|
start_instance() {
|
||||||
local enable key host port listener proto mtuauto chacha dev mode
|
local enable key host port listener proto mtuauto chacha dev mode
|
||||||
local persist kxtimeout timetolerance keepalive
|
local persist kxtimeout timetolerance keepalive localip remoteip
|
||||||
local key_dir="/var/run/${PROG_NAME}"
|
local key_dir="/var/run/${PROG_NAME}"
|
||||||
local key_file="${key_dir}/${1}.key"
|
local key_file="${key_dir}/${1}.key"
|
||||||
|
|
||||||
@@ -106,10 +108,11 @@ start_instance() {
|
|||||||
|
|
||||||
procd_close_instance
|
procd_close_instance
|
||||||
|
|
||||||
# kxtimeout, timetolerance and keepalive are not bind arguments: only
|
# kxtimeout, timetolerance, keepalive and the tunnel addresses are not
|
||||||
# the ifup hotplug set them, and a restart that keeps the device raises
|
# bind arguments: only the ifup hotplug set them, and a restart that
|
||||||
# no ifup, nor does a change of them alone (procd keeps the instance).
|
# keeps the device raises no ifup, nor does a change of them alone
|
||||||
# Set them once the tunnel answers; the delay leaves procd the time to
|
# (procd keeps the instance). Set them once the tunnel answers (ifconfig
|
||||||
|
# leaves unchanged addresses alone); the delay leaves procd the time to
|
||||||
# replace an instance it restarts. fd 1000 is procd.sh's service lock:
|
# replace an instance it restarts. fd 1000 is procd.sh's service lock:
|
||||||
# kept open, it would block every other glorytun-udp init call meanwhile
|
# kept open, it would block every other glorytun-udp init call meanwhile
|
||||||
(
|
(
|
||||||
@@ -120,6 +123,8 @@ start_instance() {
|
|||||||
[ "$count" -gt 10 ] && exit 0
|
[ "$count" -gt 10 ] && exit 0
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
[ -n "$localip" ] && [ -n "$remoteip" ] &&
|
||||||
|
ifconfig "$dev" "$localip" pointopoint "$remoteip" up
|
||||||
${PROG} set dev "$dev" kxtimeout "$kxtimeout" timetolerance "$timetolerance" keepalive "$keepalive"
|
${PROG} set dev "$dev" kxtimeout "$kxtimeout" timetolerance "$timetolerance" keepalive "$keepalive"
|
||||||
) </dev/null >/dev/null 2>&1 1000>&- &
|
) </dev/null >/dev/null 2>&1 1000>&- &
|
||||||
|
|
||||||
@@ -153,8 +158,8 @@ start_service() {
|
|||||||
|
|
||||||
# No reload_service: stop;start there restarted the tunnel on every network
|
# No reload_service: stop;start there restarted the tunnel on every network
|
||||||
# config change. The default (start) lets procd restart only an instance whose
|
# config change. The default (start) lets procd restart only an instance whose
|
||||||
# command or key file changed; kxtimeout, timetolerance and keepalive are
|
# command or key file changed; kxtimeout, timetolerance, keepalive and the
|
||||||
# applied by start_instance either way.
|
# tunnel addresses are applied by start_instance either way.
|
||||||
service_triggers() {
|
service_triggers() {
|
||||||
procd_add_reload_trigger glorytun-udp network
|
procd_add_reload_trigger glorytun-udp network
|
||||||
}
|
}
|
||||||
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=gowebdav
|
PKG_NAME:=gowebdav
|
||||||
PKG_VERSION:=5.17.1
|
PKG_VERSION:=5.17.2
|
||||||
PKG_RELEASE:=26
|
PKG_RELEASE:=27
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=headscale
|
PKG_NAME:=headscale
|
||||||
PKG_VERSION:=0.29.5
|
PKG_VERSION:=0.29.6
|
||||||
PKG_RELEASE:=10
|
PKG_RELEASE:=11
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/juanfont/headscale/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/juanfont/headscale/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"luci-app-dsvpn": {
|
"luci-app-dsvpn": {
|
||||||
"description": "Grant access to DSVPN",
|
"description": "Grant access to DSVPN",
|
||||||
"read": {
|
"read": {
|
||||||
|
"ubus": {
|
||||||
|
"luci-rpc": [ "getHostHints" ]
|
||||||
|
},
|
||||||
"uci": [ "dsvpn" ]
|
"uci": [ "dsvpn" ]
|
||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"luci-app-glorytun-tcp": {
|
"luci-app-glorytun-tcp": {
|
||||||
"description": "Grant access to glorytun TCP",
|
"description": "Grant access to glorytun TCP",
|
||||||
"read": {
|
"read": {
|
||||||
|
"ubus": {
|
||||||
|
"luci-rpc": [ "getHostHints" ]
|
||||||
|
},
|
||||||
"uci": [ "glorytun" ]
|
"uci": [ "glorytun" ]
|
||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
|
|||||||
@@ -8,13 +8,6 @@
|
|||||||
|
|
||||||
var callHostHints;
|
var callHostHints;
|
||||||
|
|
||||||
var callServiceList = rpc.declare({
|
|
||||||
object: 'service',
|
|
||||||
method: 'list',
|
|
||||||
params: [ 'name' ],
|
|
||||||
expect: { '': {} }
|
|
||||||
});
|
|
||||||
|
|
||||||
return L.view.extend({
|
return L.view.extend({
|
||||||
callHostHints: rpc.declare({
|
callHostHints: rpc.declare({
|
||||||
object: 'luci-rpc',
|
object: 'luci-rpc',
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"luci-app-glorytun-udp": {
|
"luci-app-glorytun-udp": {
|
||||||
"description": "Grant access to glorytun UDP",
|
"description": "Grant access to glorytun UDP",
|
||||||
"read": {
|
"read": {
|
||||||
|
"ubus": {
|
||||||
|
"luci-rpc": [ "getHostHints" ]
|
||||||
|
},
|
||||||
"uci": [ "glorytun-udp" ]
|
"uci": [ "glorytun-udp" ]
|
||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
|
|||||||
@@ -63,17 +63,28 @@ config_account() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start_service() {
|
start_service() {
|
||||||
local msmtp_tmp msmtp_written=0
|
local msmtp_tmp msmtp_new msmtp_written=0
|
||||||
# The file holds the SMTP password: created 0600 next to the target so
|
# The file holds the SMTP password: always created 0600. Built in tmpfs
|
||||||
# the mv is a rename, never readable by others even for a moment
|
# and only written to flash when it changed, every start and reload
|
||||||
msmtp_tmp="$(umask 077; mktemp /etc/msmtprc.XXXXXX)" || return 1
|
# rewrote /etc/msmtprc
|
||||||
|
msmtp_tmp="$(umask 077; mktemp /tmp/msmtprc.XXXXXX)" || return 1
|
||||||
config_load mail
|
config_load mail
|
||||||
config_foreach config_account smtp
|
config_foreach config_account smtp
|
||||||
if [ "$msmtp_written" = "1" ]; then
|
if [ "$msmtp_written" = "1" ] && ! cmp -s "$msmtp_tmp" /etc/msmtprc; then
|
||||||
mv "$msmtp_tmp" /etc/msmtprc
|
# next to the target so the mv is a rename, never readable by
|
||||||
else
|
# others even for a moment; keeps the owner and mode of the old
|
||||||
rm -f "$msmtp_tmp"
|
# file, but never its bits for others
|
||||||
|
msmtp_new="$(umask 077; mktemp /etc/msmtprc.XXXXXX)" &&
|
||||||
|
cat "$msmtp_tmp" > "$msmtp_new" && {
|
||||||
|
[ -f /etc/msmtprc ] && {
|
||||||
|
chown "$(stat -c %u:%g /etc/msmtprc)" "$msmtp_new"
|
||||||
|
chmod "$(stat -c %a /etc/msmtprc)" "$msmtp_new"
|
||||||
|
chmod o-rwx "$msmtp_new"
|
||||||
|
}
|
||||||
|
mv "$msmtp_new" /etc/msmtprc
|
||||||
|
} || rm -f "$msmtp_new"
|
||||||
fi
|
fi
|
||||||
|
rm -f "$msmtp_tmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
service_triggers() {
|
service_triggers() {
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"luci-app-mlvpn": {
|
"luci-app-mlvpn": {
|
||||||
"description": "Grant UCI access for luci-app-mlvpn",
|
"description": "Grant UCI access for luci-app-mlvpn",
|
||||||
"read": {
|
"read": {
|
||||||
|
"ubus": {
|
||||||
|
"luci-rpc": [ "getHostHints" ]
|
||||||
|
},
|
||||||
"uci": [ "mlvpn" ]
|
"uci": [ "mlvpn" ]
|
||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ var callSystemBoard = rpc.declare({
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 040-multipath-weight's reading of network.<iface>.multipath_weight:
|
||||||
|
// not a number is unset (100), 0 is 1, over 256 is 256
|
||||||
|
function normalizeWeightValue(value) {
|
||||||
|
if (value == null || value === '')
|
||||||
|
return value;
|
||||||
|
if (!/^[0-9]+$/.test(value))
|
||||||
|
return null;
|
||||||
|
value = +value;
|
||||||
|
return String(value < 1 ? 1 : (value > 256 ? 256 : value));
|
||||||
|
}
|
||||||
|
|
||||||
return L.view.extend({
|
return L.view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
@@ -400,6 +411,13 @@ return L.view.extend({
|
|||||||
// none. Unset means 100 for every reader (omr-weight-sync, mqvpn).
|
// none. Unset means 100 for every reader (omr-weight-sync, mqvpn).
|
||||||
o.placeholder = "100";
|
o.placeholder = "100";
|
||||||
o.rmempty = true;
|
o.rmempty = true;
|
||||||
|
// A weight stored out of range (0, 300) failed the datatype and
|
||||||
|
// blocked every save of this page: shown as 040-multipath-weight reads
|
||||||
|
// it. A save that doesn't change the field leaves a number unwritten
|
||||||
|
// and drops what is not one
|
||||||
|
o.cfgvalue = function(section_id) {
|
||||||
|
return normalizeWeightValue(uci.get('network', section_id, 'multipath_weight'));
|
||||||
|
};
|
||||||
|
|
||||||
return m.render();
|
return m.render();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,19 @@ return L.view.extend({
|
|||||||
// multipath_weight='100' on each WAN that had none (unset is 100)
|
// multipath_weight='100' on each WAN that had none (unset is 100)
|
||||||
o.placeholder = '100';
|
o.placeholder = '100';
|
||||||
o.rmempty = true;
|
o.rmempty = true;
|
||||||
|
// A weight stored out of range (0, 300) failed the datatype
|
||||||
|
// and blocked every save: shown as 040-multipath-weight reads
|
||||||
|
// it (not a number: unset, 0: 1, over 256: 256). A save that
|
||||||
|
// doesn't change the field leaves a number unwritten
|
||||||
|
o.cfgvalue = function(section_id) {
|
||||||
|
var value = uci.get('network', section_id, 'multipath_weight');
|
||||||
|
if (value == null || value === '')
|
||||||
|
return value;
|
||||||
|
if (!/^[0-9]+$/.test(value))
|
||||||
|
return null;
|
||||||
|
value = +value;
|
||||||
|
return String(value < 1 ? 1 : (value > 256 ? 256 : value));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.render();
|
return m.render();
|
||||||
|
|||||||
@@ -612,6 +612,52 @@ local function valid_name(v)
|
|||||||
return v:match("^[%w_]+$") ~= nil
|
return v:match("^[%w_]+$") ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set_settings / set_interface_settings committed without applying: nothing
|
||||||
|
-- changed until the next reload, and a second master was dropped by it (the
|
||||||
|
-- init script keeps the first one in config order). As setmptcpconfig of
|
||||||
|
-- the openmptcprouter object does: the other masters are demoted here, and
|
||||||
|
-- mptcp reloads in the background.
|
||||||
|
local function mptcp_reload(device)
|
||||||
|
local cmd = "/etc/init.d/mptcp reload"
|
||||||
|
-- embedded bare inside the sh -c single quotes
|
||||||
|
if device and device:match("^[%w_%.%-]+$") then cmd = cmd .. " " .. device end
|
||||||
|
os.execute("sh -c '(sleep 1; " .. cmd .. ") >/dev/null 2>/dev/null &'")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_multipath(iface, mp)
|
||||||
|
local previous = uci:get("network", iface, "multipath") or ""
|
||||||
|
if previous == "" then
|
||||||
|
-- /etc/init.d/mptcp falls back to the openmptcprouter copy
|
||||||
|
previous = uci:get("openmptcprouter", iface, "multipath") or ""
|
||||||
|
end
|
||||||
|
local demoted = 0
|
||||||
|
if mp == "master" then
|
||||||
|
uci:foreach("network", "interface", function(s)
|
||||||
|
if s[".name"] ~= iface and s["multipath"] == "master" then
|
||||||
|
uci:set("network", s[".name"], "multipath", "on")
|
||||||
|
if uci:get("openmptcprouter", s[".name"]) ~= nil then
|
||||||
|
uci:set("openmptcprouter", s[".name"], "multipath", "on")
|
||||||
|
end
|
||||||
|
demoted = demoted + 1
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
uci:set("network", iface, "multipath", mp)
|
||||||
|
-- what omr-tracker puts back on a WAN it brings up again
|
||||||
|
if uci:get("openmptcprouter", iface) == nil then
|
||||||
|
uci:set("openmptcprouter", iface, "interface")
|
||||||
|
end
|
||||||
|
uci:set("openmptcprouter", iface, "multipath", mp)
|
||||||
|
uci:save("openmptcprouter")
|
||||||
|
uci:commit("openmptcprouter")
|
||||||
|
if previous == mp and demoted == 0 then return nil end
|
||||||
|
-- master changes need the unscoped pass, which alone enforces one master
|
||||||
|
if demoted > 0 or (previous == "master") ~= (mp == "master") then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
return get_device(iface)
|
||||||
|
end
|
||||||
|
|
||||||
local setting_checks = {
|
local setting_checks = {
|
||||||
mptcp_checksum = function(v) return v == "0" or v == "1" end,
|
mptcp_checksum = function(v) return v == "0" or v == "1" end,
|
||||||
mptcp_debug = function(v) return v == "0" or v == "1" end,
|
mptcp_debug = function(v) return v == "0" or v == "1" end,
|
||||||
@@ -751,18 +797,22 @@ local methods = {
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local changed = false
|
||||||
for _, field in ipairs(string_fields) do
|
for _, field in ipairs(string_fields) do
|
||||||
if args[field] ~= nil and args[field] ~= "" then
|
if args[field] ~= nil and args[field] ~= "" then
|
||||||
|
changed = changed or uci:get("network", "globals", field) ~= args[field]
|
||||||
uci:set("network", "globals", field, args[field])
|
uci:set("network", "globals", field, args[field])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _, field in ipairs(list_fields) do
|
for _, field in ipairs(list_fields) do
|
||||||
if args[field] ~= nil and type(args[field]) == "table" then
|
if args[field] ~= nil and type(args[field]) == "table" then
|
||||||
|
changed = true
|
||||||
uci:set_list("network", "globals", field, args[field])
|
uci:set_list("network", "globals", field, args[field])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
uci:save("network")
|
uci:save("network")
|
||||||
uci:commit("network")
|
uci:commit("network")
|
||||||
|
if changed then mptcp_reload() end
|
||||||
return { result = "ok" }
|
return { result = "ok" }
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
@@ -822,14 +872,17 @@ local methods = {
|
|||||||
not valid_weight(args.multipath_weight) then
|
not valid_weight(args.multipath_weight) then
|
||||||
return { error = "Invalid value for multipath_weight" }
|
return { error = "Invalid value for multipath_weight" }
|
||||||
end
|
end
|
||||||
|
-- the weight needs no reload: the post-tracking hooks apply it
|
||||||
|
local reload
|
||||||
if mp ~= "" then
|
if mp ~= "" then
|
||||||
uci:set("network", iface, "multipath", mp)
|
reload = set_multipath(iface, mp)
|
||||||
end
|
end
|
||||||
if args.multipath_weight ~= nil and args.multipath_weight ~= "" then
|
if args.multipath_weight ~= nil and args.multipath_weight ~= "" then
|
||||||
uci:set("network", iface, "multipath_weight", args.multipath_weight)
|
uci:set("network", iface, "multipath_weight", args.multipath_weight)
|
||||||
end
|
end
|
||||||
uci:save("network")
|
uci:save("network")
|
||||||
uci:commit("network")
|
uci:commit("network")
|
||||||
|
if reload then mptcp_reload(reload) end
|
||||||
return { result = "ok" }
|
return { result = "ok" }
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-open-box
|
PKG_NAME:=luci-app-open-box
|
||||||
PKG_VERSION:=0.1.356
|
PKG_VERSION:=0.1.359
|
||||||
PKG_RELEASE:=44
|
PKG_RELEASE:=45
|
||||||
|
|
||||||
PKG_MAINTAINER:=liandu2024
|
PKG_MAINTAINER:=liandu2024
|
||||||
PKG_LICENSE:=GPL-3.0-or-later
|
PKG_LICENSE:=GPL-3.0-or-later
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.10.22
|
PKG_VERSION:=26.10.22
|
||||||
PKG_RELEASE:=347
|
PKG_RELEASE:=348
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -0,0 +1,773 @@
|
|||||||
|
/*
|
||||||
|
* Click-to-sort headers and a per-group filter bar for the node list.
|
||||||
|
*
|
||||||
|
* Sort reorders rows inside one group table and calls set_order(), the same
|
||||||
|
* path as drag-and-drop and To Top. Nothing is written until Save & Apply.
|
||||||
|
*
|
||||||
|
* A filter only toggles the pw-row-hidden class. Rows stay in the form, so
|
||||||
|
* Save & Apply still posts every node and the full group.order value.
|
||||||
|
*
|
||||||
|
* Translated strings are read from #pw-nodesort-i18n (the node list template).
|
||||||
|
* The template calls init() once, then enhanceAll() after loadNodeList()
|
||||||
|
* has built the group tables.
|
||||||
|
*
|
||||||
|
* auto_detection_time is a top-level let in the template, not a window
|
||||||
|
* property. It is read by name so rows are not moved while automatic Ping
|
||||||
|
* or TCPing is still writing results by row position.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var COLUMNS = {
|
||||||
|
'pw-remark': 'name',
|
||||||
|
'pw-ping': 'ping',
|
||||||
|
'pw-tcping': 'tcping',
|
||||||
|
'pw-urltest': 'urltest'
|
||||||
|
};
|
||||||
|
/* sub controls drawn inside the Remarks header: [sort key, label key or symbol, title key] */
|
||||||
|
var NAME_EXTRA = [
|
||||||
|
['type', 'type', 'typeHint'],
|
||||||
|
['addr', 'addr', 'addrHint'],
|
||||||
|
['orig', '\u21BA', 'origHint']
|
||||||
|
];
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
app: 'passwall',
|
||||||
|
root: null,
|
||||||
|
text: {},
|
||||||
|
bound: false
|
||||||
|
};
|
||||||
|
var searchTimer = null;
|
||||||
|
|
||||||
|
function T(key) {
|
||||||
|
var value = state.text[key];
|
||||||
|
return (value == null || value === '') ? key : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readText(box) {
|
||||||
|
var text = {};
|
||||||
|
var spans = box.querySelectorAll('[data-k]');
|
||||||
|
for (var i = 0; i < spans.length; i++)
|
||||||
|
text[spans[i].getAttribute('data-k')] = spans[i].textContent;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
var collator = null;
|
||||||
|
function prepareCollator() {
|
||||||
|
var lang = document.documentElement.lang || (navigator.language || '');
|
||||||
|
try {
|
||||||
|
collator = (window.Intl && Intl.Collator) ? new Intl.Collator(lang || undefined, { numeric: true, sensitivity: 'base' }) : null;
|
||||||
|
} catch (e) {
|
||||||
|
collator = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmpText(a, b) {
|
||||||
|
if (collator)
|
||||||
|
return collator.compare(a, b);
|
||||||
|
return a < b ? -1 : (a > b ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "123 ms", "12.5 ms", "1.2 s" -> milliseconds. Timeout, Error, ---, Test stay null. */
|
||||||
|
function parseLatency(text) {
|
||||||
|
var t = String(text || '').replace(/\s+/g, ' ').trim();
|
||||||
|
var m = /^(\d+(?:[.,]\d+)?)\s*(ms|s)?$/i.exec(t);
|
||||||
|
if (!m)
|
||||||
|
return null;
|
||||||
|
var v = parseFloat(m[1].replace(',', '.'));
|
||||||
|
if (isNaN(v))
|
||||||
|
return null;
|
||||||
|
if (m[2] && /^s$/i.test(m[2]))
|
||||||
|
v *= 1000;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hiddenVal(id, opt) {
|
||||||
|
var el = document.getElementById('cbid.' + state.app + '.' + id + '.' + opt);
|
||||||
|
return el && el.value != null ? String(el.value).trim() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellsOf(tr) {
|
||||||
|
var out = [];
|
||||||
|
for (var i = 0; i < tr.children.length; i++) {
|
||||||
|
var tag = tr.children[i].tagName;
|
||||||
|
if (tag === 'TD' || tag === 'TH')
|
||||||
|
out.push(tr.children[i]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wordSplit;
|
||||||
|
try {
|
||||||
|
wordSplit = new RegExp('[^\\p{L}\\p{N}]+', 'u');
|
||||||
|
} catch (e) {
|
||||||
|
wordSplit = /[\s.,;:!?|\/\\()\[\]{}<>"'`~@#$%^&*=+_-]+/;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wordsOf(text) {
|
||||||
|
var seen = {};
|
||||||
|
var out = [];
|
||||||
|
var parts = String(text || '').split(wordSplit);
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
var w = parts[i];
|
||||||
|
if (w.length < 2 || /^\d+$/.test(w))
|
||||||
|
continue;
|
||||||
|
var k = w.toLowerCase();
|
||||||
|
if (!seen[k]) {
|
||||||
|
seen[k] = 1;
|
||||||
|
out.push(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function infoOf(table) {
|
||||||
|
var prefix = 'cbi-' + state.app + '-nodes-';
|
||||||
|
var id = table && table.id ? table.id : '';
|
||||||
|
if (id.indexOf(prefix) !== 0 || id.slice(-6) !== '-table')
|
||||||
|
return null;
|
||||||
|
return { group: id.slice(prefix.length, -6) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowId(tr) {
|
||||||
|
var prefix = 'cbi-' + state.app + '-';
|
||||||
|
return tr.id.indexOf(prefix) === 0 ? tr.id.substring(prefix.length) : tr.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bodyOf(table) {
|
||||||
|
return table.querySelector('tbody') || table;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dataRows(table) {
|
||||||
|
var body = bodyOf(table);
|
||||||
|
var out = [];
|
||||||
|
for (var i = 0; i < body.children.length; i++) {
|
||||||
|
var tr = body.children[i];
|
||||||
|
if (tr.tagName === 'TR' && tr.classList.contains('cbi-section-table-row') && !tr.classList.contains('placeholder'))
|
||||||
|
out.push(tr);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function colIndexMap(table) {
|
||||||
|
var rows = dataRows(table);
|
||||||
|
var map = {};
|
||||||
|
if (!rows.length)
|
||||||
|
return map;
|
||||||
|
var cells = cellsOf(rows[0]);
|
||||||
|
for (var i = 0; i < cells.length; i++) {
|
||||||
|
for (var cls in COLUMNS) {
|
||||||
|
if (cells[i].classList.contains(cls))
|
||||||
|
map[COLUMNS[cls]] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeOf(tr, cols) {
|
||||||
|
var cell = cellsOf(tr)[cols.name];
|
||||||
|
var text = cell ? cell.textContent.replace(/\s+/g, ' ').trim() : '';
|
||||||
|
var i = text.indexOf('\uFF1A');
|
||||||
|
if (i < 0)
|
||||||
|
i = text.indexOf(':');
|
||||||
|
return (i < 0 ? '' : text.slice(0, i)).replace(/\s+Realm$/, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortKey(tr, key, cols) {
|
||||||
|
var id = rowId(tr);
|
||||||
|
var cell;
|
||||||
|
if (key === 'name')
|
||||||
|
return hiddenVal(id, 'remarks') || ((cellsOf(tr)[cols.name] || {}).textContent || '').trim();
|
||||||
|
if (key === 'type')
|
||||||
|
return typeOf(tr, cols) + '\u0000' + hiddenVal(id, 'remarks');
|
||||||
|
if (key === 'addr') {
|
||||||
|
var address = hiddenVal(id, 'address');
|
||||||
|
if (!address)
|
||||||
|
return null;
|
||||||
|
return address + '\u0000' + ('00000' + hiddenVal(id, 'port')).slice(-5);
|
||||||
|
}
|
||||||
|
cell = cellsOf(tr)[cols[key]];
|
||||||
|
return cell ? parseLatency(cell.textContent) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectionMode() {
|
||||||
|
try {
|
||||||
|
if (typeof auto_detection_time === 'undefined')
|
||||||
|
return null;
|
||||||
|
return auto_detection_time;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* { inflight: requests still running, left: cells still waiting for a result } */
|
||||||
|
function pingBusy() {
|
||||||
|
var mode = detectionMode();
|
||||||
|
var inflight = 0;
|
||||||
|
var left = 0;
|
||||||
|
if (mode !== 'icmp' && mode !== 'tcping')
|
||||||
|
return { inflight: 0, left: 0 };
|
||||||
|
var list = window.ajax && window.ajax.list;
|
||||||
|
if (list && list.length) {
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
if (list[i].readyState > 0 && list[i].readyState < 4)
|
||||||
|
inflight++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!state.root)
|
||||||
|
return { inflight: inflight, left: 0 };
|
||||||
|
var spans = state.root.getElementsByClassName(mode === 'tcping' ? 'tcping_value' : 'ping_value');
|
||||||
|
for (var s = 0; s < spans.length; s++) {
|
||||||
|
var id = spans[s].getAttribute('cbiid');
|
||||||
|
if (!id || spans[s].textContent.trim() !== '---')
|
||||||
|
continue;
|
||||||
|
var full = null;
|
||||||
|
if (typeof window.get_address_full === 'function')
|
||||||
|
full = window.get_address_full(id);
|
||||||
|
if (!full)
|
||||||
|
full = { address: hiddenVal(id, 'address'), port: hiddenVal(id, 'port') };
|
||||||
|
if (full.address && (mode === 'icmp' || full.port))
|
||||||
|
left++;
|
||||||
|
}
|
||||||
|
return { inflight: inflight, left: left };
|
||||||
|
}
|
||||||
|
|
||||||
|
function note(table, text) {
|
||||||
|
var id = 'pw-note-' + table.id;
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (!text) {
|
||||||
|
if (el && el.parentNode)
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!el) {
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.id = id;
|
||||||
|
el.className = 'pw-sort-note';
|
||||||
|
table.parentNode.insertBefore(el, table);
|
||||||
|
}
|
||||||
|
el.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIndicator(table, th, sub, key, dir) {
|
||||||
|
var arrows = table.querySelectorAll('.pw-sort-arrow');
|
||||||
|
var subs = table.querySelectorAll('.pw-sort-sub');
|
||||||
|
var headers = table.querySelectorAll('th[data-pw-sort]');
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < arrows.length; i++)
|
||||||
|
arrows[i].textContent = '';
|
||||||
|
for (i = 0; i < subs.length; i++)
|
||||||
|
subs[i].classList.remove('pw-sort-active');
|
||||||
|
for (i = 0; i < headers.length; i++) {
|
||||||
|
if (headers[i] !== th) {
|
||||||
|
delete headers[i].dataset.pwDir;
|
||||||
|
delete headers[i].dataset.pwKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var arrow = th.querySelector('.pw-sort-arrow');
|
||||||
|
if (arrow && key !== 'orig' && !sub)
|
||||||
|
arrow.textContent = dir === 'desc' ? '\u25BC' : '\u25B2';
|
||||||
|
if (sub && key !== 'orig')
|
||||||
|
sub.classList.add('pw-sort-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyOrder(table, info, rows) {
|
||||||
|
var body = bodyOf(table);
|
||||||
|
var frag = document.createDocumentFragment();
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
rows[i].classList.remove('cbi-rowstyle-1', 'cbi-rowstyle-2');
|
||||||
|
rows[i].classList.add('cbi-rowstyle-' + (i % 2 + 1));
|
||||||
|
frag.appendChild(rows[i]);
|
||||||
|
}
|
||||||
|
body.appendChild(frag);
|
||||||
|
if (typeof window.set_order === 'function')
|
||||||
|
window.set_order(info.group);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSort(table, th, sub, key) {
|
||||||
|
var info = infoOf(table);
|
||||||
|
var rows = dataRows(table);
|
||||||
|
var dir = 'asc';
|
||||||
|
if (!info || rows.length < 2)
|
||||||
|
return;
|
||||||
|
if (key === 'orig') {
|
||||||
|
var pos = {};
|
||||||
|
var orig = table.__pwOrig || [];
|
||||||
|
for (var n = 0; n < orig.length; n++)
|
||||||
|
pos[orig[n]] = n;
|
||||||
|
rows.sort(function (a, b) {
|
||||||
|
var pa = pos[rowId(a)];
|
||||||
|
var pb = pos[rowId(b)];
|
||||||
|
return (pa == null ? 1e9 : pa) - (pb == null ? 1e9 : pb);
|
||||||
|
});
|
||||||
|
delete th.dataset.pwDir;
|
||||||
|
delete th.dataset.pwKey;
|
||||||
|
} else {
|
||||||
|
dir = (th.dataset.pwKey === key && th.dataset.pwDir === 'asc') ? 'desc' : 'asc';
|
||||||
|
th.dataset.pwKey = key;
|
||||||
|
th.dataset.pwDir = dir;
|
||||||
|
var cols = colIndexMap(table);
|
||||||
|
var sign = dir === 'desc' ? -1 : 1;
|
||||||
|
var list = [];
|
||||||
|
for (var i = 0; i < rows.length; i++)
|
||||||
|
list.push({ tr: rows[i], i: i, k: sortKey(rows[i], key, cols) });
|
||||||
|
list.sort(function (a, b) {
|
||||||
|
var emptyA = (a.k === null || a.k === '');
|
||||||
|
var emptyB = (b.k === null || b.k === '');
|
||||||
|
if (emptyA || emptyB)
|
||||||
|
return emptyA === emptyB ? a.i - b.i : (emptyA ? 1 : -1);
|
||||||
|
var cmp = (typeof a.k === 'number' && typeof b.k === 'number') ? a.k - b.k : cmpText(String(a.k), String(b.k));
|
||||||
|
return cmp !== 0 ? sign * cmp : a.i - b.i;
|
||||||
|
});
|
||||||
|
rows = [];
|
||||||
|
for (var j = 0; j < list.length; j++)
|
||||||
|
rows.push(list[j].tr);
|
||||||
|
}
|
||||||
|
applyOrder(table, info, rows);
|
||||||
|
refreshFilter(table);
|
||||||
|
setIndicator(table, th, sub, key, dir);
|
||||||
|
note(table, key === 'orig' ? T('restored') : T('changed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestSort(table, th, sub, key) {
|
||||||
|
if (table.__pwWait) {
|
||||||
|
clearInterval(table.__pwWait);
|
||||||
|
table.__pwWait = null;
|
||||||
|
}
|
||||||
|
var st = pingBusy();
|
||||||
|
if (!st.inflight && !st.left) {
|
||||||
|
doSort(table, th, sub, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var started = Date.now();
|
||||||
|
var idle = 0;
|
||||||
|
note(table, T('wait').replace('%d', st.left || st.inflight));
|
||||||
|
table.__pwWait = setInterval(function () {
|
||||||
|
st = pingBusy();
|
||||||
|
idle = st.inflight ? 0 : idle + 1;
|
||||||
|
if ((!st.inflight && !st.left) || idle >= 6) {
|
||||||
|
clearInterval(table.__pwWait);
|
||||||
|
table.__pwWait = null;
|
||||||
|
if (document.body.contains(table))
|
||||||
|
doSort(table, th, sub, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Date.now() - started > 120000) {
|
||||||
|
clearInterval(table.__pwWait);
|
||||||
|
table.__pwWait = null;
|
||||||
|
note(table, T('waitFail'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
note(table, T('wait').replace('%d', st.left || st.inflight));
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enhance(table) {
|
||||||
|
var info = infoOf(table);
|
||||||
|
if (!info || table.__pwReady)
|
||||||
|
return;
|
||||||
|
var titles = table.querySelector('tr.cbi-section-table-titles');
|
||||||
|
var rows = dataRows(table);
|
||||||
|
if (!titles || !rows.length)
|
||||||
|
return;
|
||||||
|
var cols = colIndexMap(table);
|
||||||
|
var headers = cellsOf(titles);
|
||||||
|
table.__pwReady = true;
|
||||||
|
table.__pwOrig = [];
|
||||||
|
for (var r = 0; r < rows.length; r++)
|
||||||
|
table.__pwOrig.push(rowId(rows[r]));
|
||||||
|
for (var key in cols) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(cols, key))
|
||||||
|
continue;
|
||||||
|
var th = headers[cols[key]];
|
||||||
|
if (!th || th.classList.contains('cbi-section-actions'))
|
||||||
|
continue;
|
||||||
|
th.setAttribute('data-pw-sort', key);
|
||||||
|
th.title = T('hint');
|
||||||
|
var arrow = document.createElement('span');
|
||||||
|
arrow.className = 'pw-sort-arrow';
|
||||||
|
th.appendChild(arrow);
|
||||||
|
if (key !== 'name')
|
||||||
|
continue;
|
||||||
|
for (var s = 0; s < NAME_EXTRA.length; s++) {
|
||||||
|
var spec = NAME_EXTRA[s];
|
||||||
|
var sp = document.createElement('span');
|
||||||
|
sp.className = 'pw-sort-sub';
|
||||||
|
sp.setAttribute('data-pw-sort', spec[0]);
|
||||||
|
sp.textContent = T(spec[1]);
|
||||||
|
sp.title = T(spec[2]);
|
||||||
|
th.appendChild(sp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowVisible(el) {
|
||||||
|
var row = el && el.closest ? el.closest('tr') : null;
|
||||||
|
return !row || !row.classList.contains('pw-row-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterDefs(rows, cols) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
label: T('type'),
|
||||||
|
multi: false,
|
||||||
|
get: function (tr) { return typeOf(tr, cols); }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'word',
|
||||||
|
label: T('fWord'),
|
||||||
|
multi: true,
|
||||||
|
minCount: 2,
|
||||||
|
max: 40,
|
||||||
|
get: function (tr) { return wordsOf(hiddenVal(rowId(tr), 'remarks')); }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchText(tr, cols) {
|
||||||
|
var id = rowId(tr);
|
||||||
|
return [
|
||||||
|
typeOf(tr, cols),
|
||||||
|
hiddenVal(id, 'remarks'),
|
||||||
|
hiddenVal(id, 'address') + ':' + hiddenVal(id, 'port')
|
||||||
|
].join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function collect(table) {
|
||||||
|
var rows = dataRows(table);
|
||||||
|
var cols = colIndexMap(table);
|
||||||
|
var defs = rows.length >= 2 ? filterDefs(rows, cols) : [];
|
||||||
|
var shown = [];
|
||||||
|
for (var d = 0; d < defs.length; d++) {
|
||||||
|
var def = defs[d];
|
||||||
|
var counts = {};
|
||||||
|
var labels = {};
|
||||||
|
var vals = [];
|
||||||
|
for (var r = 0; r < rows.length; r++) {
|
||||||
|
var raw = def.get(rows[r]);
|
||||||
|
var list = def.multi ? (raw || []) : [raw == null ? '' : String(raw)];
|
||||||
|
var keys = [];
|
||||||
|
for (var n = 0; n < list.length; n++) {
|
||||||
|
var item = list[n];
|
||||||
|
var k = def.multi ? String(item).toLowerCase() : String(item);
|
||||||
|
counts[k] = (counts[k] || 0) + 1;
|
||||||
|
if (!labels[k])
|
||||||
|
labels[k] = {};
|
||||||
|
labels[k][item] = (labels[k][item] || 0) + 1;
|
||||||
|
keys.push(k);
|
||||||
|
}
|
||||||
|
vals.push(keys);
|
||||||
|
}
|
||||||
|
var opts = [];
|
||||||
|
for (var k in counts) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(counts, k))
|
||||||
|
continue;
|
||||||
|
if (def.multi && (counts[k] < (def.minCount || 1) || counts[k] >= rows.length))
|
||||||
|
continue;
|
||||||
|
var best = '';
|
||||||
|
var bestN = -1;
|
||||||
|
for (var label in labels[k]) {
|
||||||
|
if (labels[k][label] > bestN) {
|
||||||
|
bestN = labels[k][label];
|
||||||
|
best = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.push({ v: k, label: best === '' ? T('empty') : best, n: counts[k] });
|
||||||
|
}
|
||||||
|
opts.sort(function (a, b) {
|
||||||
|
if (def.multi && a.n !== b.n)
|
||||||
|
return b.n - a.n;
|
||||||
|
return cmpText(a.label, b.label);
|
||||||
|
});
|
||||||
|
if (def.max && opts.length > def.max)
|
||||||
|
opts = opts.slice(0, def.max);
|
||||||
|
var enough = def.multi ? opts.length >= 1 : opts.length >= 2;
|
||||||
|
if (!enough)
|
||||||
|
continue;
|
||||||
|
def.vals = vals;
|
||||||
|
def.opts = opts;
|
||||||
|
shown.push(def);
|
||||||
|
}
|
||||||
|
var search = rows.length >= 2;
|
||||||
|
var sig = String(rows.length) + (search ? 's' : '');
|
||||||
|
for (var i = 0; i < shown.length; i++) {
|
||||||
|
sig += '|' + shown[i].key + '=';
|
||||||
|
for (var o = 0; o < shown[i].opts.length; o++)
|
||||||
|
sig += shown[i].opts[o].v + ':' + shown[i].opts[o].n + ',';
|
||||||
|
}
|
||||||
|
return { rows: rows, cols: cols, defs: shown, search: search, sig: sig };
|
||||||
|
}
|
||||||
|
|
||||||
|
function barId(table) {
|
||||||
|
return 'pw-filter-' + table.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBar(table, collected) {
|
||||||
|
var st = table.__pwF || (table.__pwF = { sel: {}, q: '' });
|
||||||
|
var id = barId(table);
|
||||||
|
var bar = document.getElementById(id);
|
||||||
|
if (!bar) {
|
||||||
|
bar = document.createElement('div');
|
||||||
|
bar.id = id;
|
||||||
|
bar.className = 'pw-filter';
|
||||||
|
bar.title = T('filterHint');
|
||||||
|
table.parentNode.insertBefore(bar, table);
|
||||||
|
bar.__pwTable = table;
|
||||||
|
}
|
||||||
|
while (bar.firstChild)
|
||||||
|
bar.removeChild(bar.firstChild);
|
||||||
|
var head = document.createElement('span');
|
||||||
|
head.textContent = T('filter');
|
||||||
|
bar.appendChild(head);
|
||||||
|
var keep = {};
|
||||||
|
for (var d = 0; d < collected.defs.length; d++) {
|
||||||
|
var def = collected.defs[d];
|
||||||
|
var lab = document.createElement('label');
|
||||||
|
var sel = document.createElement('select');
|
||||||
|
lab.appendChild(document.createTextNode(def.label));
|
||||||
|
sel.className = 'cbi-input-select';
|
||||||
|
sel.setAttribute('data-pw-filter', def.key);
|
||||||
|
var all = document.createElement('option');
|
||||||
|
all.value = '';
|
||||||
|
all.textContent = T('all') + ' (' + collected.rows.length + ')';
|
||||||
|
sel.appendChild(all);
|
||||||
|
for (var o = 0; o < def.opts.length; o++) {
|
||||||
|
var op = document.createElement('option');
|
||||||
|
op.value = 'v:' + def.opts[o].v;
|
||||||
|
op.textContent = def.opts[o].label + ' (' + def.opts[o].n + ')';
|
||||||
|
sel.appendChild(op);
|
||||||
|
}
|
||||||
|
var cur = st.sel[def.key] || '';
|
||||||
|
var known = false;
|
||||||
|
if (cur) {
|
||||||
|
for (var c = 0; c < def.opts.length; c++) {
|
||||||
|
if (('v:' + def.opts[c].v) === cur) {
|
||||||
|
known = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!known)
|
||||||
|
cur = '';
|
||||||
|
sel.value = cur;
|
||||||
|
if (cur)
|
||||||
|
keep[def.key] = cur;
|
||||||
|
lab.appendChild(sel);
|
||||||
|
bar.appendChild(lab);
|
||||||
|
}
|
||||||
|
st.sel = keep;
|
||||||
|
if (collected.search) {
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.type = 'search';
|
||||||
|
input.className = 'cbi-input-text';
|
||||||
|
input.placeholder = T('search');
|
||||||
|
input.title = T('searchHint');
|
||||||
|
input.autocomplete = 'off';
|
||||||
|
input.setAttribute('data-pw-search', '1');
|
||||||
|
input.value = st.q;
|
||||||
|
bar.appendChild(input);
|
||||||
|
} else {
|
||||||
|
st.q = '';
|
||||||
|
}
|
||||||
|
var count = document.createElement('span');
|
||||||
|
count.className = 'pw-filter-count';
|
||||||
|
bar.appendChild(count);
|
||||||
|
var reset = document.createElement('button');
|
||||||
|
reset.type = 'button';
|
||||||
|
reset.className = 'btn cbi-button';
|
||||||
|
reset.setAttribute('data-pw-reset', '1');
|
||||||
|
reset.textContent = T('reset');
|
||||||
|
reset.title = T('resetHint');
|
||||||
|
bar.appendChild(reset);
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function listHas(list, value) {
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
if (list[i] === value)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilter(table, collected, bar) {
|
||||||
|
var st = table.__pwF || { sel: {}, q: '' };
|
||||||
|
var q = String(st.q || '').toLowerCase().trim();
|
||||||
|
var active = !!q;
|
||||||
|
var shown = 0;
|
||||||
|
var d;
|
||||||
|
for (d = 0; d < collected.defs.length; d++) {
|
||||||
|
if (st.sel[collected.defs[d].key])
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < collected.rows.length; i++) {
|
||||||
|
var tr = collected.rows[i];
|
||||||
|
var ok = true;
|
||||||
|
for (d = 0; d < collected.defs.length; d++) {
|
||||||
|
var sel = st.sel[collected.defs[d].key];
|
||||||
|
if (ok && sel && !listHas(collected.defs[d].vals[i], sel.slice(2)))
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (ok && q && searchText(tr, collected.cols).toLowerCase().indexOf(q) < 0)
|
||||||
|
ok = false;
|
||||||
|
if (ok !== !tr.classList.contains('pw-row-hidden'))
|
||||||
|
tr.classList.toggle('pw-row-hidden', !ok);
|
||||||
|
if (ok) {
|
||||||
|
shown++;
|
||||||
|
var want = 'cbi-rowstyle-' + ((shown - 1) % 2 + 1);
|
||||||
|
var other = want === 'cbi-rowstyle-1' ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1';
|
||||||
|
if (!tr.classList.contains(want))
|
||||||
|
tr.classList.add(want);
|
||||||
|
if (tr.classList.contains(other))
|
||||||
|
tr.classList.remove(other);
|
||||||
|
} else {
|
||||||
|
var cb = tr.querySelector('input.nodes_select');
|
||||||
|
if (cb && cb.checked) {
|
||||||
|
cb.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof window.update_select_state === 'function')
|
||||||
|
window.update_select_state();
|
||||||
|
var count = bar.querySelector('.pw-filter-count');
|
||||||
|
var txt = T('shown').replace('%s', shown).replace('%t', collected.rows.length);
|
||||||
|
if (count && count.textContent !== txt)
|
||||||
|
count.textContent = txt;
|
||||||
|
if (bar.classList.contains('pw-filter-on') !== active)
|
||||||
|
bar.classList.toggle('pw-filter-on', active);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBar(table) {
|
||||||
|
var bar = document.getElementById(barId(table));
|
||||||
|
if (bar && bar.parentNode)
|
||||||
|
bar.parentNode.removeChild(bar);
|
||||||
|
var rows = dataRows(table);
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
if (rows[i].classList.contains('pw-row-hidden'))
|
||||||
|
rows[i].classList.remove('pw-row-hidden');
|
||||||
|
}
|
||||||
|
table.__pwSig = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFilter(table) {
|
||||||
|
if (!infoOf(table))
|
||||||
|
return;
|
||||||
|
var collected = collect(table);
|
||||||
|
if (!collected.defs.length && !collected.search) {
|
||||||
|
if (table.__pwSig)
|
||||||
|
removeBar(table);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var bar = document.getElementById(barId(table));
|
||||||
|
if (!bar || collected.sig !== table.__pwSig) {
|
||||||
|
bar = buildBar(table, collected);
|
||||||
|
table.__pwSig = collected.sig;
|
||||||
|
}
|
||||||
|
applyFilter(table, collected, bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function barTable(el) {
|
||||||
|
var bar = el && el.closest ? el.closest('.pw-filter') : null;
|
||||||
|
return bar && bar.__pwTable ? bar.__pwTable : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventTarget(ev) {
|
||||||
|
var t = ev.target;
|
||||||
|
if (t && t.nodeType !== 1)
|
||||||
|
t = t.parentNode;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(root) {
|
||||||
|
root.addEventListener('click', function (ev) {
|
||||||
|
var t = eventTarget(ev);
|
||||||
|
if (t && t.getAttribute && t.getAttribute('data-pw-reset')) {
|
||||||
|
var resetTable = barTable(t);
|
||||||
|
if (!resetTable)
|
||||||
|
return;
|
||||||
|
ev.preventDefault();
|
||||||
|
resetTable.__pwF = { sel: {}, q: '' };
|
||||||
|
resetTable.__pwSig = null;
|
||||||
|
refreshFilter(resetTable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var el = t && t.closest ? t.closest('[data-pw-sort]') : null;
|
||||||
|
if (!el || !root.contains(el))
|
||||||
|
return;
|
||||||
|
if (t.closest && t.closest('input, a, button, select'))
|
||||||
|
return;
|
||||||
|
var th = el.closest('th');
|
||||||
|
var table = el.closest('table');
|
||||||
|
if (!th || !table || !infoOf(table))
|
||||||
|
return;
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
requestSort(table, th, el === th ? null : el, el.getAttribute('data-pw-sort'));
|
||||||
|
});
|
||||||
|
root.addEventListener('change', function (ev) {
|
||||||
|
var t = eventTarget(ev);
|
||||||
|
if (!t || !t.getAttribute || !t.getAttribute('data-pw-filter'))
|
||||||
|
return;
|
||||||
|
var table = barTable(t);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
(table.__pwF = table.__pwF || { sel: {}, q: '' }).sel[t.getAttribute('data-pw-filter')] = t.value;
|
||||||
|
refreshFilter(table);
|
||||||
|
});
|
||||||
|
root.addEventListener('input', function (ev) {
|
||||||
|
var t = eventTarget(ev);
|
||||||
|
if (!t || !t.getAttribute || !t.getAttribute('data-pw-search'))
|
||||||
|
return;
|
||||||
|
var table = barTable(t);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
clearTimeout(searchTimer);
|
||||||
|
searchTimer = setTimeout(function () {
|
||||||
|
(table.__pwF = table.__pwF || { sel: {}, q: '' }).q = t.value;
|
||||||
|
refreshFilter(table);
|
||||||
|
}, 150);
|
||||||
|
});
|
||||||
|
root.addEventListener('keydown', function (ev) {
|
||||||
|
var t = eventTarget(ev);
|
||||||
|
if (ev.key === 'Enter' && t && t.getAttribute && t.getAttribute('data-pw-search'))
|
||||||
|
ev.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var box = document.getElementById('pw-nodesort-i18n');
|
||||||
|
var root = document.getElementById('node_list');
|
||||||
|
if (!box || !root)
|
||||||
|
return;
|
||||||
|
state.app = box.getAttribute('data-app') || 'passwall';
|
||||||
|
state.text = readText(box);
|
||||||
|
state.root = root;
|
||||||
|
prepareCollator();
|
||||||
|
if (!state.bound) {
|
||||||
|
bind(root);
|
||||||
|
state.bound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enhanceAll() {
|
||||||
|
if (!state.root)
|
||||||
|
return;
|
||||||
|
var tables = state.root.querySelectorAll('table.cbi-section-table');
|
||||||
|
for (var i = 0; i < tables.length; i++) {
|
||||||
|
try {
|
||||||
|
enhance(tables[i]);
|
||||||
|
refreshFilter(tables[i]);
|
||||||
|
} catch (e) {
|
||||||
|
if (window.console)
|
||||||
|
console.warn('passwall node list sort:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.pwNodeSort = {
|
||||||
|
init: init,
|
||||||
|
enhanceAll: enhanceAll,
|
||||||
|
rowVisible: rowVisible
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -78,7 +78,7 @@ if has_xray or has_singbox then
|
|||||||
o.default = 0
|
o.default = 0
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
o.description = translate("Generate rule lists such as GFW, China domains, and China IP ranges based on Geo files.") .. "<br><font color='red'>" ..
|
o.description = translate("Generate rule lists such as GFW, China domains, and China IP ranges based on Geo files.") .. "<br><font color='red'>" ..
|
||||||
translate("When manually updating with this option enabled, rules will be regenerated from existing Geo files even if no new version is available.") .. "</font>"
|
translate("GeoSite files without GFW rules cannot generate a GFW list (e.g. the original v2fly/Xray files).") .. "</font>"
|
||||||
|
|
||||||
o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing"))
|
o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing"))
|
||||||
o.default = 0
|
o.default = 0
|
||||||
|
|||||||
@@ -1692,8 +1692,21 @@ function gen_config(var)
|
|||||||
local bin = api.finded_com("geoview")
|
local bin = api.finded_com("geoview")
|
||||||
if bin then
|
if bin then
|
||||||
local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
|
local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
|
||||||
if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then
|
local gfw_rule
|
||||||
domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw"
|
if sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" 2>/dev/null | grep -q "^GFW$"') == 0 then
|
||||||
|
gfw_rule = "geosite:gfw"
|
||||||
|
elseif api.compare_versions(api.get_app_version("geoview"), ">=", "0.2.8") then
|
||||||
|
local output = GEO_VAR.TO_SRS_PATH .. "geosite-gfw.srs"
|
||||||
|
if not fs.access(output) then
|
||||||
|
fs.mkdirr(GEO_VAR.TO_SRS_PATH)
|
||||||
|
local cmd = string.format("%s -type geosite -action convert -input /usr/share/passwall/rules/gfwlist -list '*' -output %s -format srs",
|
||||||
|
api.util.shellquote(bin), api.util.shellquote(output))
|
||||||
|
if api.exec_call(cmd) ~= 0 then fs.unlink(output) end
|
||||||
|
end
|
||||||
|
gfw_rule = fs.access(output) and ("rule-set:local:" .. output) or nil
|
||||||
|
end
|
||||||
|
if gfw_rule then
|
||||||
|
domain_list = (domain_list == "") and gfw_rule or domain_list .. "\n" .. gfw_rule
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -579,11 +579,15 @@ function gen_config_server(node)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if node.protocol == "vmess" or node.protocol == "vless" then
|
if node.protocol == "vless" then
|
||||||
|
settings = {
|
||||||
|
users = users,
|
||||||
|
decryption = (node.decryption and node.decryption ~= "") and node.decryption or "none"
|
||||||
|
}
|
||||||
|
elseif node.protocol == "vmess" then
|
||||||
if users then
|
if users then
|
||||||
settings = {
|
settings = {
|
||||||
users = users,
|
users = users
|
||||||
decryption = (node.protocol == "vless") and ((node.decryption and node.decryption ~= "") and node.decryption or "none") or nil
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
elseif node.protocol == "socks" then
|
elseif node.protocol == "socks" then
|
||||||
@@ -1479,8 +1483,21 @@ function gen_config(var)
|
|||||||
local bin = api.finded_com("geoview")
|
local bin = api.finded_com("geoview")
|
||||||
if bin then
|
if bin then
|
||||||
local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
|
local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
|
||||||
if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then
|
local gfw_rule
|
||||||
domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw"
|
if sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" 2>/dev/null | grep -q "^GFW$"') == 0 then
|
||||||
|
gfw_rule = "geosite:gfw"
|
||||||
|
elseif api.compare_versions(api.get_app_version("geoview"), ">=", "0.2.8") then
|
||||||
|
local output = api.CACHE_PATH .. "/geodata/gfwlist.dat"
|
||||||
|
if not fs.access(output) then
|
||||||
|
fs.mkdirr(api.CACHE_PATH .. "/geodata")
|
||||||
|
local cmd = string.format("%s -type geosite -action convert -input /usr/share/passwall/rules/gfwlist -list '*' -output %s -format geosite",
|
||||||
|
api.util.shellquote(bin), api.util.shellquote(output))
|
||||||
|
if api.exec_call(cmd) ~= 0 then fs.unlink(output) end
|
||||||
|
end
|
||||||
|
gfw_rule = fs.access(output) and "ext:gfwlist.dat:all" or nil
|
||||||
|
end
|
||||||
|
if gfw_rule then
|
||||||
|
domain_list = (domain_list == "") and gfw_rule or domain_list .. "\n" .. gfw_rule
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1539,7 +1556,7 @@ function gen_config(var)
|
|||||||
w = api.trim(w)
|
w = api.trim(w)
|
||||||
if w == "" or w:find("#") == 1 then return end
|
if w == "" or w:find("#") == 1 then return end
|
||||||
if w:find("rule-set:", 1, true) == 1 or w:find("rs:") == 1 then return end
|
if w:find("rule-set:", 1, true) == 1 or w:find("rs:") == 1 then return end
|
||||||
if w:find("ext:", 1, true) == 1 then return end -- Rule currently not support ext:
|
if w:find("ext:", 1, true) == 1 and not (e[".name"] == "GFW_Mode_List" and w == "ext:gfwlist.dat:all") then return end
|
||||||
table.insert(domains, w)
|
table.insert(domains, w)
|
||||||
table.insert(domain_table.domain, w)
|
table.insert(domain_table.domain, w)
|
||||||
end)
|
end)
|
||||||
@@ -2051,10 +2068,7 @@ function gen_config(var)
|
|||||||
|
|
||||||
if inbounds or outbounds then
|
if inbounds or outbounds then
|
||||||
local config = {
|
local config = {
|
||||||
env = (function()
|
env = { XRAY_LOCATION_ASSET = api.CACHE_PATH .. "/geodata" },
|
||||||
local asset_location = api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
|
|
||||||
return { XRAY_LOCATION_ASSET = asset_location }
|
|
||||||
end)(),
|
|
||||||
log = {
|
log = {
|
||||||
-- error = string.format("/tmp/etc/passwall/%s.log", node[".name"]),
|
-- error = string.format("/tmp/etc/passwall/%s.log", node[".name"]),
|
||||||
loglevel = get_log_level(loglevel)
|
loglevel = get_log_level(loglevel)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ if node then
|
|||||||
end
|
end
|
||||||
-%>
|
-%>
|
||||||
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js?v=1.15.7"></script>
|
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js?v=1.15.7"></script>
|
||||||
|
<script src="<%=resource%>/view/<%=appname%>/node_list_sort.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
table th, .table .th {
|
table th, .table .th {
|
||||||
@@ -385,6 +386,89 @@ table td, .table .td {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* node list: click-to-sort headers and the per-group filter bar */
|
||||||
|
th[data-pw-sort] {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
th[data-pw-sort]:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-sort-arrow {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 1em;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-sort-sub {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 85%;
|
||||||
|
opacity: 0.85;
|
||||||
|
margin-left: 0.6em;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-sort-sub.pw-sort-active {
|
||||||
|
opacity: 1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-sort-note {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
padding: 0.3em 0.6em;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(251, 154, 5, 0.15);
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.pw-row-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3em 0.8em;
|
||||||
|
margin: 0.4em 0;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter select,
|
||||||
|
.pw-filter input[type="search"],
|
||||||
|
.pw-filter .cbi-button {
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 6em;
|
||||||
|
max-width: 22em;
|
||||||
|
height: auto;
|
||||||
|
padding: 0.15em 0.3em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter .pw-filter-count {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter.pw-filter-on .pw-filter-count {
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
<% if is_js_luci then -%>
|
<% if is_js_luci then -%>
|
||||||
var cbi_t = [];
|
var cbi_t = [];
|
||||||
@@ -540,15 +624,29 @@ table td, .table .td {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function node_row_visible(el) {
|
||||||
|
if (window.pwNodeSort && typeof pwNodeSort.rowVisible === "function")
|
||||||
|
return pwNodeSort.rowVisible(el);
|
||||||
|
var row = el && el.closest ? el.closest("tr") : null;
|
||||||
|
return !row || !row.classList.contains("pw-row-hidden");
|
||||||
|
}
|
||||||
|
|
||||||
function set_select_all_state(sectionChecked) {
|
function set_select_all_state(sectionChecked) {
|
||||||
var visibleContainer = document.querySelector('#cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display: block"]');
|
var visibleContainer = document.querySelector('#cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||||
if (!visibleContainer) return;
|
if (!visibleContainer) return;
|
||||||
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
||||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||||
var selectAllBtn = document.getElementById("select_all_btn");
|
var selectAllBtn = document.getElementById("select_all_btn");
|
||||||
|
var visibleCount = 0;
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
if (!node_row_visible(nodes[i])) {
|
||||||
|
nodes[i].checked = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
nodes[i].checked = sectionChecked;
|
nodes[i].checked = sectionChecked;
|
||||||
|
visibleCount++;
|
||||||
}
|
}
|
||||||
|
sectionChecked = sectionChecked && visibleCount > 0;
|
||||||
if (selectAllChk) {
|
if (selectAllChk) {
|
||||||
selectAllChk.checked = sectionChecked;
|
selectAllChk.checked = sectionChecked;
|
||||||
selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||||
@@ -576,10 +674,13 @@ table td, .table .td {
|
|||||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||||
var selectAllBtn = document.getElementById("select_all_btn");
|
var selectAllBtn = document.getElementById("select_all_btn");
|
||||||
var checkedCount = 0;
|
var checkedCount = 0;
|
||||||
|
var visibleCount = 0;
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
if (!node_row_visible(nodes[i])) continue;
|
||||||
|
visibleCount++;
|
||||||
if (nodes[i].checked) checkedCount++;
|
if (nodes[i].checked) checkedCount++;
|
||||||
}
|
}
|
||||||
var allChecked = checkedCount === nodes.length;
|
var allChecked = visibleCount > 0 && checkedCount === visibleCount;
|
||||||
var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||||
var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)";
|
var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)";
|
||||||
|
|
||||||
@@ -602,7 +703,7 @@ table td, .table .td {
|
|||||||
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||||
if (doms && doms.length > 0) {
|
if (doms && doms.length > 0) {
|
||||||
for (var i = 0 ; i < doms.length; i++) {
|
for (var i = 0 ; i < doms.length; i++) {
|
||||||
if (doms[i].checked) {
|
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||||
ids.push(doms[i].getAttribute("cbid"))
|
ids.push(doms[i].getAttribute("cbid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1163,7 +1264,7 @@ table td, .table .td {
|
|||||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||||
if (doms && doms.length > 0) {
|
if (doms && doms.length > 0) {
|
||||||
for (let i = 0 ; i < doms.length; i++) {
|
for (let i = 0 ; i < doms.length; i++) {
|
||||||
if (doms[i].checked) {
|
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||||
ids.push(doms[i].getAttribute("cbid"))
|
ids.push(doms[i].getAttribute("cbid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1195,7 +1296,7 @@ table td, .table .td {
|
|||||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||||
if (doms && doms.length > 0) {
|
if (doms && doms.length > 0) {
|
||||||
for (let i = 0 ; i < doms.length; i++) {
|
for (let i = 0 ; i < doms.length; i++) {
|
||||||
if (doms[i].checked) {
|
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||||
ids.push(doms[i].getAttribute("cbid"))
|
ids.push(doms[i].getAttribute("cbid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1307,8 +1408,33 @@ table td, .table .td {
|
|||||||
<<%=section_tag%> class="cbi-section" id="node_list">
|
<<%=section_tag%> class="cbi-section" id="node_list">
|
||||||
</<%=section_tag%>>
|
</<%=section_tag%>>
|
||||||
|
|
||||||
|
<div id="pw-nodesort-i18n" data-app="<%=appname%>" style="display:none">
|
||||||
|
<span data-k="hint"><%:Click to sort; click again to reverse. Press Save & Apply to keep the order.%></span>
|
||||||
|
<span data-k="typeHint"><%:Sort by type or protocol, then by name%></span>
|
||||||
|
<span data-k="addrHint"><%:Sort by server address and port%></span>
|
||||||
|
<span data-k="origHint"><%:Restore the saved order%></span>
|
||||||
|
<span data-k="changed"><%:The order changed on this page only. Press Save & Apply to store it on the router.%></span>
|
||||||
|
<span data-k="restored"><%:Saved order restored.%></span>
|
||||||
|
<span data-k="wait"><%:Automatic ping is still running (%d left). Sorting will run when it finishes.%></span>
|
||||||
|
<span data-k="waitFail"><%:Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again.%></span>
|
||||||
|
<span data-k="filter"><%:Filter:%></span>
|
||||||
|
<span data-k="all"><%:All%></span>
|
||||||
|
<span data-k="empty"><%:(empty)%></span>
|
||||||
|
<span data-k="type"><%:Type%></span>
|
||||||
|
<span data-k="addr"><%:Address%></span>
|
||||||
|
<span data-k="fWord"><%:Word in remarks%></span>
|
||||||
|
<span data-k="search"><%:search...%></span>
|
||||||
|
<span data-k="searchHint"><%:Show only rows that contain this text%></span>
|
||||||
|
<span data-k="shown"><%:Shown %s of %t%></span>
|
||||||
|
<span data-k="reset"><%:Reset%></span>
|
||||||
|
<span data-k="resetHint"><%:Show all rows%></span>
|
||||||
|
<span data-k="filterHint"><%:The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows.%></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
//init logic
|
//init logic
|
||||||
|
if (window.pwNodeSort)
|
||||||
|
pwNodeSort.init();
|
||||||
|
|
||||||
function get_remarks_name(o) {
|
function get_remarks_name(o) {
|
||||||
let str = "";
|
let str = "";
|
||||||
@@ -1511,6 +1637,8 @@ table td, .table .td {
|
|||||||
get_now_use_node();
|
get_now_use_node();
|
||||||
|
|
||||||
pingAllNodes();
|
pingAllNodes();
|
||||||
|
if (window.pwNodeSort)
|
||||||
|
pwNodeSort.enhanceAll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -974,8 +974,8 @@ msgstr "从 Geo 文件生成规则"
|
|||||||
msgid "Generate rule lists such as GFW, China domains, and China IP ranges based on Geo files."
|
msgid "Generate rule lists such as GFW, China domains, and China IP ranges based on Geo files."
|
||||||
msgstr "根据 Geo 文件生成规则列表,包括 GFW、中国域名和中国 IP 段等。"
|
msgstr "根据 Geo 文件生成规则列表,包括 GFW、中国域名和中国 IP 段等。"
|
||||||
|
|
||||||
msgid "When manually updating with this option enabled, rules will be regenerated from existing Geo files even if no new version is available."
|
msgid "GeoSite files without GFW rules cannot generate a GFW list (e.g. the original v2fly/Xray files)."
|
||||||
msgstr "启用此选项后手动更新时,即使没有新版本也会从现有 Geo 文件重新生成规则。"
|
msgstr "不含 GFW 规则的 GeoSite 文件无法生成 GFW 列表(如 v2fly/Xray 原版)。"
|
||||||
|
|
||||||
msgid "Enable Geo Data Parsing"
|
msgid "Enable Geo Data Parsing"
|
||||||
msgstr "开启 Geo 数据解析"
|
msgstr "开启 Geo 数据解析"
|
||||||
@@ -2048,3 +2048,57 @@ msgstr "WARP 设备私钥"
|
|||||||
|
|
||||||
msgid "WARP Endpoint Public Key"
|
msgid "WARP Endpoint Public Key"
|
||||||
msgstr "WARP 服务端公钥"
|
msgstr "WARP 服务端公钥"
|
||||||
|
|
||||||
|
msgid "Click to sort; click again to reverse. Press Save & Apply to keep the order."
|
||||||
|
msgstr "点击表头排序,再次点击切换方向。按保存并应用可保留顺序。"
|
||||||
|
|
||||||
|
msgid "Sort by type or protocol, then by name"
|
||||||
|
msgstr "先按类型或协议排序,再按名称排序"
|
||||||
|
|
||||||
|
msgid "Sort by server address and port"
|
||||||
|
msgstr "按服务器地址和端口排序"
|
||||||
|
|
||||||
|
msgid "Restore the saved order"
|
||||||
|
msgstr "恢复已保存的顺序"
|
||||||
|
|
||||||
|
msgid "The order changed on this page only. Press Save & Apply to store it on the router."
|
||||||
|
msgstr "顺序仅在本页改变。按保存并应用才会写入路由器。"
|
||||||
|
|
||||||
|
msgid "Saved order restored."
|
||||||
|
msgstr "已恢复保存的顺序。"
|
||||||
|
|
||||||
|
msgid "Automatic ping is still running (%d left). Sorting will run when it finishes."
|
||||||
|
msgstr "自动探测仍在进行(剩余 %d)。结束后会立即排序。"
|
||||||
|
|
||||||
|
msgid "Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again."
|
||||||
|
msgstr "自动探测在 2 分钟内未结束,已取消排序。请刷新页面后重试。"
|
||||||
|
|
||||||
|
msgid "Filter:"
|
||||||
|
msgstr "筛选:"
|
||||||
|
|
||||||
|
msgid "(empty)"
|
||||||
|
msgstr "(空)"
|
||||||
|
|
||||||
|
msgid "Word in remarks"
|
||||||
|
msgstr "备注关键词"
|
||||||
|
|
||||||
|
msgid "Address"
|
||||||
|
msgstr "地址"
|
||||||
|
|
||||||
|
msgid "search..."
|
||||||
|
msgstr "搜索..."
|
||||||
|
|
||||||
|
msgid "Show only rows that contain this text"
|
||||||
|
msgstr "只显示包含此文本的行"
|
||||||
|
|
||||||
|
msgid "Shown %s of %t"
|
||||||
|
msgstr "显示 %s / %t"
|
||||||
|
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr "重置"
|
||||||
|
|
||||||
|
msgid "Show all rows"
|
||||||
|
msgstr "显示全部行"
|
||||||
|
|
||||||
|
msgid "The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows."
|
||||||
|
msgstr "筛选只在本页隐藏行,不会改动路由器上的设置。全选会跳过隐藏行。"
|
||||||
@@ -640,8 +640,8 @@ start_global() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[ "$protocol" = "_shunt" ] && {
|
[ "$protocol" = "_shunt" ] && {
|
||||||
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
|
local geoip_path="${GEO_DIR%/}/geoip.dat"
|
||||||
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
|
local geosite_path="${GEO_DIR%/}/geosite.dat"
|
||||||
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
|
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
|
||||||
echolog "* 缺少Geo规则文件,TCP Sing-Box分流节点无法正常使用!"
|
echolog "* 缺少Geo规则文件,TCP Sing-Box分流节点无法正常使用!"
|
||||||
fi
|
fi
|
||||||
@@ -724,8 +724,8 @@ start_global() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[ "$protocol" = "_shunt" ] && {
|
[ "$protocol" = "_shunt" ] && {
|
||||||
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
|
local geoip_path="${GEO_DIR%/}/geoip.dat"
|
||||||
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
|
local geosite_path="${GEO_DIR%/}/geosite.dat"
|
||||||
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
|
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
|
||||||
echolog "* 缺少Geo规则文件,TCP Xray分流节点无法正常使用!"
|
echolog "* 缺少Geo规则文件,TCP Xray分流节点无法正常使用!"
|
||||||
fi
|
fi
|
||||||
@@ -1718,8 +1718,14 @@ start() {
|
|||||||
mkdir -p /tmp/etc /tmp/log $TMP_PATH $TMP_BIN_PATH $TMP_SCRIPT_FUNC_PATH $TMP_ROUTE_PATH $TMP_ACL_PATH $TMP_PATH2
|
mkdir -p /tmp/etc /tmp/log $TMP_PATH $TMP_BIN_PATH $TMP_SCRIPT_FUNC_PATH $TMP_ROUTE_PATH $TMP_ACL_PATH $TMP_PATH2
|
||||||
set_cache_var "LOCAL_DNS" "$LOCAL_DNS"
|
set_cache_var "LOCAL_DNS" "$LOCAL_DNS"
|
||||||
get_config
|
get_config
|
||||||
export V2RAY_LOCATION_ASSET=$(config_n_get @global_rules[0] v2ray_location_asset "/usr/share/v2ray/")
|
GEO_DIR=$(config_n_get @global_rules[0] v2ray_location_asset "/usr/share/v2ray/")
|
||||||
export XRAY_LOCATION_ASSET=$V2RAY_LOCATION_ASSET
|
export XRAY_LOCATION_ASSET="$TMP_PATH2/geodata"
|
||||||
|
mkdir -p "$XRAY_LOCATION_ASSET" 2>/dev/null
|
||||||
|
local geo_file
|
||||||
|
for geo_file in "${GEO_DIR%/}"/*.dat; do
|
||||||
|
[ -f "$geo_file" ] || continue
|
||||||
|
ln -sf "$geo_file" "$XRAY_LOCATION_ASSET/${geo_file##*/}" 2>/dev/null
|
||||||
|
done
|
||||||
export ENABLE_DEPRECATED_GEOSITE=true
|
export ENABLE_DEPRECATED_GEOSITE=true
|
||||||
export ENABLE_DEPRECATED_GEOIP=true
|
export ENABLE_DEPRECATED_GEOIP=true
|
||||||
export SS_SYSTEM_DNS_RESOLVER_FORCE_BUILTIN=1
|
export SS_SYSTEM_DNS_RESOLVER_FORCE_BUILTIN=1
|
||||||
@@ -1800,7 +1806,6 @@ stop() {
|
|||||||
busybox pgrep -af "${CONFIG}/monitor\.sh" | xargs -r kill -9 >/dev/null 2>&1
|
busybox pgrep -af "${CONFIG}/monitor\.sh" | xargs -r kill -9 >/dev/null 2>&1
|
||||||
busybox pgrep -f "sleep.*(6s|9s|58s)" | xargs -r kill -9 >/dev/null 2>&1
|
busybox pgrep -f "sleep.*(6s|9s|58s)" | xargs -r kill -9 >/dev/null 2>&1
|
||||||
busybox pgrep -af "${CONFIG}/" | awk '! /app\.sh|subscribe\.lua|rule_update\.lua|tasks\.sh|server_app\.lua|ujail/{print $1}' | xargs -r kill -9 >/dev/null 2>&1
|
busybox pgrep -af "${CONFIG}/" | awk '! /app\.sh|subscribe\.lua|rule_update\.lua|tasks\.sh|server_app\.lua|ujail/{print $1}' | xargs -r kill -9 >/dev/null 2>&1
|
||||||
unset V2RAY_LOCATION_ASSET
|
|
||||||
unset XRAY_LOCATION_ASSET
|
unset XRAY_LOCATION_ASSET
|
||||||
unset SS_SYSTEM_DNS_RESOLVER_FORCE_BUILTIN
|
unset SS_SYSTEM_DNS_RESOLVER_FORCE_BUILTIN
|
||||||
stop_crontab "$1"
|
stop_crontab "$1"
|
||||||
|
|||||||
@@ -1597,6 +1597,7 @@ stop() {
|
|||||||
rm -rf $TMP_PATH2/singbox*
|
rm -rf $TMP_PATH2/singbox*
|
||||||
rm -rf $TMP_PATH2/dnsmasq*
|
rm -rf $TMP_PATH2/dnsmasq*
|
||||||
rm -rf $TMP_PATH2/geo_output
|
rm -rf $TMP_PATH2/geo_output
|
||||||
|
rm -rf $TMP_PATH2/geodata
|
||||||
rm -rf $TMP_PATH2/user_rules
|
rm -rf $TMP_PATH2/user_rules
|
||||||
}
|
}
|
||||||
flush_include
|
flush_include
|
||||||
|
|||||||
@@ -1610,6 +1610,7 @@ stop() {
|
|||||||
rm -rf $TMP_PATH2/singbox*
|
rm -rf $TMP_PATH2/singbox*
|
||||||
rm -rf $TMP_PATH2/dnsmasq*
|
rm -rf $TMP_PATH2/dnsmasq*
|
||||||
rm -rf $TMP_PATH2/geo_output
|
rm -rf $TMP_PATH2/geo_output
|
||||||
|
rm -rf $TMP_PATH2/geodata
|
||||||
rm -rf $TMP_PATH2/user_rules
|
rm -rf $TMP_PATH2/user_rules
|
||||||
}
|
}
|
||||||
flush_include
|
flush_include
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall2
|
PKG_NAME:=luci-app-passwall2
|
||||||
PKG_VERSION:=26.10.20
|
PKG_VERSION:=26.10.20
|
||||||
PKG_RELEASE:=149
|
PKG_RELEASE:=150
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -0,0 +1,775 @@
|
|||||||
|
/*
|
||||||
|
* Click-to-sort headers and a per-group filter bar for the node list.
|
||||||
|
*
|
||||||
|
* Sort reorders rows inside one group table and calls set_order(), the same
|
||||||
|
* path as drag-and-drop and To Top. Nothing is written until Save & Apply.
|
||||||
|
*
|
||||||
|
* A filter only toggles the pw-row-hidden class. Rows stay in the form, so
|
||||||
|
* Save & Apply still posts every node and the full group.order value.
|
||||||
|
*
|
||||||
|
* Translated strings are read from #pw2-nodesort-i18n (the node list template).
|
||||||
|
* The template calls init() once, then enhanceAll() after loadNodeList()
|
||||||
|
* has built the group tables.
|
||||||
|
*
|
||||||
|
* auto_detection_time is a top-level let in the template, not a window
|
||||||
|
* property. It is read by name so rows are not moved while automatic Ping
|
||||||
|
* or TCPing is still writing results by row position.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var COLUMNS = {
|
||||||
|
'pw-remark': 'name',
|
||||||
|
'pw-ping': 'ping',
|
||||||
|
'pw-tcping': 'tcping',
|
||||||
|
'pw-urltest': 'urltest'
|
||||||
|
};
|
||||||
|
/* sub controls drawn inside the Remarks header: [sort key, label key or symbol, title key] */
|
||||||
|
var NAME_EXTRA = [
|
||||||
|
['type', 'type', 'typeHint'],
|
||||||
|
['addr', 'addr', 'addrHint'],
|
||||||
|
['orig', '\u21BA', 'origHint']
|
||||||
|
];
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
app: 'passwall2',
|
||||||
|
root: null,
|
||||||
|
text: {},
|
||||||
|
bound: false
|
||||||
|
};
|
||||||
|
var searchTimer = null;
|
||||||
|
|
||||||
|
function T(key) {
|
||||||
|
var value = state.text[key];
|
||||||
|
return (value == null || value === '') ? key : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readText(box) {
|
||||||
|
var text = {};
|
||||||
|
var spans = box.querySelectorAll('[data-k]');
|
||||||
|
for (var i = 0; i < spans.length; i++)
|
||||||
|
text[spans[i].getAttribute('data-k')] = spans[i].textContent;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
var collator = null;
|
||||||
|
function prepareCollator() {
|
||||||
|
var lang = document.documentElement.lang || (navigator.language || '');
|
||||||
|
try {
|
||||||
|
collator = (window.Intl && Intl.Collator) ? new Intl.Collator(lang || undefined, { numeric: true, sensitivity: 'base' }) : null;
|
||||||
|
} catch (e) {
|
||||||
|
collator = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmpText(a, b) {
|
||||||
|
if (collator)
|
||||||
|
return collator.compare(a, b);
|
||||||
|
return a < b ? -1 : (a > b ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "123 ms", "12.5 ms", "1.2 s" -> milliseconds. Timeout, Error, ---, Test stay null. */
|
||||||
|
function parseLatency(text) {
|
||||||
|
var t = String(text || '').replace(/\s+/g, ' ').trim();
|
||||||
|
var m = /^(\d+(?:[.,]\d+)?)\s*(ms|s)?$/i.exec(t);
|
||||||
|
if (!m)
|
||||||
|
return null;
|
||||||
|
var v = parseFloat(m[1].replace(',', '.'));
|
||||||
|
if (isNaN(v))
|
||||||
|
return null;
|
||||||
|
if (m[2] && /^s$/i.test(m[2]))
|
||||||
|
v *= 1000;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hiddenVal(id, opt) {
|
||||||
|
var el = document.getElementById('cbid.' + state.app + '.' + id + '.' + opt);
|
||||||
|
return el && el.value != null ? String(el.value).trim() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellsOf(tr) {
|
||||||
|
var out = [];
|
||||||
|
for (var i = 0; i < tr.children.length; i++) {
|
||||||
|
var tag = tr.children[i].tagName;
|
||||||
|
if (tag === 'TD' || tag === 'TH')
|
||||||
|
out.push(tr.children[i]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wordSplit;
|
||||||
|
try {
|
||||||
|
wordSplit = new RegExp('[^\\p{L}\\p{N}]+', 'u');
|
||||||
|
} catch (e) {
|
||||||
|
wordSplit = /[\s.,;:!?|\/\\()\[\]{}<>"'`~@#$%^&*=+_-]+/;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wordsOf(text) {
|
||||||
|
var seen = {};
|
||||||
|
var out = [];
|
||||||
|
var parts = String(text || '').split(wordSplit);
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
var w = parts[i];
|
||||||
|
if (w.length < 2 || /^\d+$/.test(w))
|
||||||
|
continue;
|
||||||
|
var k = w.toLowerCase();
|
||||||
|
if (!seen[k]) {
|
||||||
|
seen[k] = 1;
|
||||||
|
out.push(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function infoOf(table) {
|
||||||
|
var prefix = 'cbi-' + state.app + '-nodes-';
|
||||||
|
var id = table && table.id ? table.id : '';
|
||||||
|
if (id.indexOf(prefix) !== 0 || id.slice(-6) !== '-table')
|
||||||
|
return null;
|
||||||
|
return { group: id.slice(prefix.length, -6) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowId(tr) {
|
||||||
|
var prefix = 'cbi-' + state.app + '-';
|
||||||
|
return tr.id.indexOf(prefix) === 0 ? tr.id.substring(prefix.length) : tr.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bodyOf(table) {
|
||||||
|
return table.querySelector('tbody') || table;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dataRows(table) {
|
||||||
|
var body = bodyOf(table);
|
||||||
|
var out = [];
|
||||||
|
for (var i = 0; i < body.children.length; i++) {
|
||||||
|
var tr = body.children[i];
|
||||||
|
if (tr.tagName === 'TR' && tr.classList.contains('cbi-section-table-row') && !tr.classList.contains('placeholder'))
|
||||||
|
out.push(tr);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function colIndexMap(table) {
|
||||||
|
var rows = dataRows(table);
|
||||||
|
var map = {};
|
||||||
|
if (!rows.length)
|
||||||
|
return map;
|
||||||
|
var cells = cellsOf(rows[0]);
|
||||||
|
for (var i = 0; i < cells.length; i++) {
|
||||||
|
for (var cls in COLUMNS) {
|
||||||
|
if (cells[i].classList.contains(cls))
|
||||||
|
map[COLUMNS[cls]] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeOf(tr, cols) {
|
||||||
|
var cell = cellsOf(tr)[cols.name];
|
||||||
|
var text = cell ? cell.textContent.replace(/\s+/g, ' ').trim() : '';
|
||||||
|
var i = text.indexOf('\uFF1A');
|
||||||
|
if (i < 0)
|
||||||
|
i = text.indexOf(':');
|
||||||
|
return (i < 0 ? '' : text.slice(0, i)).replace(/\s+Realm$/, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortKey(tr, key, cols) {
|
||||||
|
var id = rowId(tr);
|
||||||
|
var cell;
|
||||||
|
if (key === 'name')
|
||||||
|
return hiddenVal(id, 'remarks') || ((cellsOf(tr)[cols.name] || {}).textContent || '').trim();
|
||||||
|
if (key === 'type')
|
||||||
|
return typeOf(tr, cols) + '\u0000' + hiddenVal(id, 'remarks');
|
||||||
|
if (key === 'addr') {
|
||||||
|
var address = hiddenVal(id, 'address');
|
||||||
|
if (!address)
|
||||||
|
return null;
|
||||||
|
return address + '\u0000' + ('00000' + hiddenVal(id, 'port')).slice(-5);
|
||||||
|
}
|
||||||
|
cell = cellsOf(tr)[cols[key]];
|
||||||
|
return cell ? parseLatency(cell.textContent) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectionMode() {
|
||||||
|
try {
|
||||||
|
if (typeof auto_detection_time === 'undefined')
|
||||||
|
return null;
|
||||||
|
return auto_detection_time;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* { inflight: requests still running, left: cells still waiting for a result } */
|
||||||
|
function pingBusy() {
|
||||||
|
var mode = detectionMode();
|
||||||
|
var inflight = 0;
|
||||||
|
var left = 0;
|
||||||
|
if (mode !== 'icmp' && mode !== 'tcping')
|
||||||
|
return { inflight: 0, left: 0 };
|
||||||
|
var list = window.ajax && window.ajax.list;
|
||||||
|
if (list && list.length) {
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
if (list[i].readyState > 0 && list[i].readyState < 4)
|
||||||
|
inflight++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!state.root)
|
||||||
|
return { inflight: inflight, left: 0 };
|
||||||
|
var spans = state.root.getElementsByClassName(mode === 'tcping' ? 'tcping_value' : 'ping_value');
|
||||||
|
for (var s = 0; s < spans.length; s++) {
|
||||||
|
var id = spans[s].getAttribute('cbiid');
|
||||||
|
if (!id || spans[s].textContent.trim() !== '---')
|
||||||
|
continue;
|
||||||
|
var full = null;
|
||||||
|
if (typeof window.get_address_full === 'function')
|
||||||
|
full = window.get_address_full(id);
|
||||||
|
if (!full)
|
||||||
|
full = { address: hiddenVal(id, 'address'), port: hiddenVal(id, 'port') };
|
||||||
|
if (full.address && (mode === 'icmp' || full.port))
|
||||||
|
left++;
|
||||||
|
}
|
||||||
|
return { inflight: inflight, left: left };
|
||||||
|
}
|
||||||
|
|
||||||
|
function note(table, text) {
|
||||||
|
var id = 'pw-note-' + table.id;
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (!text) {
|
||||||
|
if (el && el.parentNode)
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!el) {
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.id = id;
|
||||||
|
el.className = 'pw-sort-note';
|
||||||
|
table.parentNode.insertBefore(el, table);
|
||||||
|
}
|
||||||
|
el.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIndicator(table, th, sub, key, dir) {
|
||||||
|
var arrows = table.querySelectorAll('.pw-sort-arrow');
|
||||||
|
var subs = table.querySelectorAll('.pw-sort-sub');
|
||||||
|
var headers = table.querySelectorAll('th[data-pw-sort]');
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < arrows.length; i++)
|
||||||
|
arrows[i].textContent = '';
|
||||||
|
for (i = 0; i < subs.length; i++)
|
||||||
|
subs[i].classList.remove('pw-sort-active');
|
||||||
|
for (i = 0; i < headers.length; i++) {
|
||||||
|
if (headers[i] !== th) {
|
||||||
|
delete headers[i].dataset.pwDir;
|
||||||
|
delete headers[i].dataset.pwKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var arrow = th.querySelector('.pw-sort-arrow');
|
||||||
|
if (arrow && key !== 'orig' && !sub)
|
||||||
|
arrow.textContent = dir === 'desc' ? '\u25BC' : '\u25B2';
|
||||||
|
if (sub && key !== 'orig')
|
||||||
|
sub.classList.add('pw-sort-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyOrder(table, info, rows) {
|
||||||
|
var body = bodyOf(table);
|
||||||
|
var frag = document.createDocumentFragment();
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
rows[i].classList.remove('cbi-rowstyle-1', 'cbi-rowstyle-2');
|
||||||
|
rows[i].classList.add('cbi-rowstyle-' + (i % 2 + 1));
|
||||||
|
frag.appendChild(rows[i]);
|
||||||
|
}
|
||||||
|
body.appendChild(frag);
|
||||||
|
if (typeof window.set_order === 'function')
|
||||||
|
window.set_order(info.group);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSort(table, th, sub, key) {
|
||||||
|
var info = infoOf(table);
|
||||||
|
var rows = dataRows(table);
|
||||||
|
var dir = 'asc';
|
||||||
|
if (!info || rows.length < 2)
|
||||||
|
return;
|
||||||
|
if (key === 'orig') {
|
||||||
|
var pos = {};
|
||||||
|
var orig = table.__pwOrig || [];
|
||||||
|
for (var n = 0; n < orig.length; n++)
|
||||||
|
pos[orig[n]] = n;
|
||||||
|
rows.sort(function (a, b) {
|
||||||
|
var pa = pos[rowId(a)];
|
||||||
|
var pb = pos[rowId(b)];
|
||||||
|
return (pa == null ? 1e9 : pa) - (pb == null ? 1e9 : pb);
|
||||||
|
});
|
||||||
|
delete th.dataset.pwDir;
|
||||||
|
delete th.dataset.pwKey;
|
||||||
|
} else {
|
||||||
|
dir = (th.dataset.pwKey === key && th.dataset.pwDir === 'asc') ? 'desc' : 'asc';
|
||||||
|
th.dataset.pwKey = key;
|
||||||
|
th.dataset.pwDir = dir;
|
||||||
|
var cols = colIndexMap(table);
|
||||||
|
var sign = dir === 'desc' ? -1 : 1;
|
||||||
|
var list = [];
|
||||||
|
for (var i = 0; i < rows.length; i++)
|
||||||
|
list.push({ tr: rows[i], i: i, k: sortKey(rows[i], key, cols) });
|
||||||
|
list.sort(function (a, b) {
|
||||||
|
var emptyA = (a.k === null || a.k === '');
|
||||||
|
var emptyB = (b.k === null || b.k === '');
|
||||||
|
if (emptyA || emptyB)
|
||||||
|
return emptyA === emptyB ? a.i - b.i : (emptyA ? 1 : -1);
|
||||||
|
var cmp = (typeof a.k === 'number' && typeof b.k === 'number') ? a.k - b.k : cmpText(String(a.k), String(b.k));
|
||||||
|
return cmp !== 0 ? sign * cmp : a.i - b.i;
|
||||||
|
});
|
||||||
|
rows = [];
|
||||||
|
for (var j = 0; j < list.length; j++)
|
||||||
|
rows.push(list[j].tr);
|
||||||
|
}
|
||||||
|
applyOrder(table, info, rows);
|
||||||
|
refreshFilter(table);
|
||||||
|
setIndicator(table, th, sub, key, dir);
|
||||||
|
note(table, key === 'orig' ? T('restored') : T('changed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestSort(table, th, sub, key) {
|
||||||
|
if (table.__pwWait) {
|
||||||
|
clearInterval(table.__pwWait);
|
||||||
|
table.__pwWait = null;
|
||||||
|
}
|
||||||
|
var st = pingBusy();
|
||||||
|
if (!st.inflight && !st.left) {
|
||||||
|
doSort(table, th, sub, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var started = Date.now();
|
||||||
|
var idle = 0;
|
||||||
|
note(table, T('wait').replace('%d', st.left || st.inflight));
|
||||||
|
table.__pwWait = setInterval(function () {
|
||||||
|
st = pingBusy();
|
||||||
|
idle = st.inflight ? 0 : idle + 1;
|
||||||
|
if ((!st.inflight && !st.left) || idle >= 6) {
|
||||||
|
clearInterval(table.__pwWait);
|
||||||
|
table.__pwWait = null;
|
||||||
|
if (document.body.contains(table))
|
||||||
|
doSort(table, th, sub, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Date.now() - started > 120000) {
|
||||||
|
clearInterval(table.__pwWait);
|
||||||
|
table.__pwWait = null;
|
||||||
|
note(table, T('waitFail'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
note(table, T('wait').replace('%d', st.left || st.inflight));
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enhance(table) {
|
||||||
|
var info = infoOf(table);
|
||||||
|
if (!info || table.__pwReady)
|
||||||
|
return;
|
||||||
|
var titles = table.querySelector('tr.cbi-section-table-titles');
|
||||||
|
var rows = dataRows(table);
|
||||||
|
if (!titles || !rows.length)
|
||||||
|
return;
|
||||||
|
var cols = colIndexMap(table);
|
||||||
|
var headers = cellsOf(titles);
|
||||||
|
table.__pwReady = true;
|
||||||
|
table.__pwOrig = [];
|
||||||
|
for (var r = 0; r < rows.length; r++)
|
||||||
|
table.__pwOrig.push(rowId(rows[r]));
|
||||||
|
for (var key in cols) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(cols, key))
|
||||||
|
continue;
|
||||||
|
var th = headers[cols[key]];
|
||||||
|
if (!th || th.classList.contains('cbi-section-actions'))
|
||||||
|
continue;
|
||||||
|
th.setAttribute('data-pw-sort', key);
|
||||||
|
th.title = T('hint');
|
||||||
|
var arrow = document.createElement('span');
|
||||||
|
arrow.className = 'pw-sort-arrow';
|
||||||
|
th.appendChild(arrow);
|
||||||
|
if (key !== 'name')
|
||||||
|
continue;
|
||||||
|
for (var s = 0; s < NAME_EXTRA.length; s++) {
|
||||||
|
var spec = NAME_EXTRA[s];
|
||||||
|
var sp = document.createElement('span');
|
||||||
|
sp.className = 'pw-sort-sub';
|
||||||
|
sp.setAttribute('data-pw-sort', spec[0]);
|
||||||
|
sp.textContent = T(spec[1]);
|
||||||
|
sp.title = T(spec[2]);
|
||||||
|
th.appendChild(sp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowVisible(el) {
|
||||||
|
var row = el && el.closest ? el.closest('tr') : null;
|
||||||
|
return !row || !row.classList.contains('pw-row-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterDefs(rows, cols) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
label: T('type'),
|
||||||
|
multi: false,
|
||||||
|
get: function (tr) { return typeOf(tr, cols); }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'word',
|
||||||
|
label: T('fWord'),
|
||||||
|
multi: true,
|
||||||
|
minCount: 2,
|
||||||
|
max: 40,
|
||||||
|
get: function (tr) { return wordsOf(hiddenVal(rowId(tr), 'remarks')); }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchText(tr, cols) {
|
||||||
|
var id = rowId(tr);
|
||||||
|
return [
|
||||||
|
typeOf(tr, cols),
|
||||||
|
hiddenVal(id, 'remarks'),
|
||||||
|
hiddenVal(id, 'address') + ':' + hiddenVal(id, 'port')
|
||||||
|
].join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function collect(table) {
|
||||||
|
var rows = dataRows(table);
|
||||||
|
var cols = colIndexMap(table);
|
||||||
|
var defs = rows.length >= 2 ? filterDefs(rows, cols) : [];
|
||||||
|
var shown = [];
|
||||||
|
for (var d = 0; d < defs.length; d++) {
|
||||||
|
var def = defs[d];
|
||||||
|
var counts = {};
|
||||||
|
var labels = {};
|
||||||
|
var vals = [];
|
||||||
|
for (var r = 0; r < rows.length; r++) {
|
||||||
|
var raw = def.get(rows[r]);
|
||||||
|
var list = def.multi ? (raw || []) : [raw == null ? '' : String(raw)];
|
||||||
|
var keys = [];
|
||||||
|
for (var n = 0; n < list.length; n++) {
|
||||||
|
var item = list[n];
|
||||||
|
var k = def.multi ? String(item).toLowerCase() : String(item);
|
||||||
|
counts[k] = (counts[k] || 0) + 1;
|
||||||
|
if (!labels[k])
|
||||||
|
labels[k] = {};
|
||||||
|
labels[k][item] = (labels[k][item] || 0) + 1;
|
||||||
|
keys.push(k);
|
||||||
|
}
|
||||||
|
vals.push(keys);
|
||||||
|
}
|
||||||
|
var opts = [];
|
||||||
|
for (var k in counts) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(counts, k))
|
||||||
|
continue;
|
||||||
|
if (def.multi && (counts[k] < (def.minCount || 1) || counts[k] >= rows.length))
|
||||||
|
continue;
|
||||||
|
var best = '';
|
||||||
|
var bestN = -1;
|
||||||
|
for (var label in labels[k]) {
|
||||||
|
if (labels[k][label] > bestN) {
|
||||||
|
bestN = labels[k][label];
|
||||||
|
best = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.push({ v: k, label: best === '' ? T('empty') : best, n: counts[k] });
|
||||||
|
}
|
||||||
|
opts.sort(function (a, b) {
|
||||||
|
if (def.multi && a.n !== b.n)
|
||||||
|
return b.n - a.n;
|
||||||
|
return cmpText(a.label, b.label);
|
||||||
|
});
|
||||||
|
if (def.max && opts.length > def.max)
|
||||||
|
opts = opts.slice(0, def.max);
|
||||||
|
var enough = def.multi ? opts.length >= 1 : opts.length >= 2;
|
||||||
|
if (!enough)
|
||||||
|
continue;
|
||||||
|
def.vals = vals;
|
||||||
|
def.opts = opts;
|
||||||
|
shown.push(def);
|
||||||
|
}
|
||||||
|
var search = rows.length >= 2;
|
||||||
|
var sig = String(rows.length) + (search ? 's' : '');
|
||||||
|
for (var i = 0; i < shown.length; i++) {
|
||||||
|
sig += '|' + shown[i].key + '=';
|
||||||
|
for (var o = 0; o < shown[i].opts.length; o++)
|
||||||
|
sig += shown[i].opts[o].v + ':' + shown[i].opts[o].n + ',';
|
||||||
|
}
|
||||||
|
return { rows: rows, cols: cols, defs: shown, search: search, sig: sig };
|
||||||
|
}
|
||||||
|
|
||||||
|
function barId(table) {
|
||||||
|
return 'pw-filter-' + table.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBar(table, collected) {
|
||||||
|
var st = table.__pwF || (table.__pwF = { sel: {}, q: '' });
|
||||||
|
var id = barId(table);
|
||||||
|
var bar = document.getElementById(id);
|
||||||
|
if (!bar) {
|
||||||
|
bar = document.createElement('div');
|
||||||
|
bar.id = id;
|
||||||
|
bar.className = 'pw-filter';
|
||||||
|
bar.title = T('filterHint');
|
||||||
|
table.parentNode.insertBefore(bar, table);
|
||||||
|
bar.__pwTable = table;
|
||||||
|
}
|
||||||
|
while (bar.firstChild)
|
||||||
|
bar.removeChild(bar.firstChild);
|
||||||
|
var head = document.createElement('span');
|
||||||
|
head.textContent = T('filter');
|
||||||
|
bar.appendChild(head);
|
||||||
|
var keep = {};
|
||||||
|
for (var d = 0; d < collected.defs.length; d++) {
|
||||||
|
var def = collected.defs[d];
|
||||||
|
var lab = document.createElement('label');
|
||||||
|
var sel = document.createElement('select');
|
||||||
|
lab.appendChild(document.createTextNode(def.label));
|
||||||
|
sel.className = 'cbi-input-select';
|
||||||
|
sel.setAttribute('data-pw-filter', def.key);
|
||||||
|
var all = document.createElement('option');
|
||||||
|
all.value = '';
|
||||||
|
all.textContent = T('all') + ' (' + collected.rows.length + ')';
|
||||||
|
sel.appendChild(all);
|
||||||
|
for (var o = 0; o < def.opts.length; o++) {
|
||||||
|
var op = document.createElement('option');
|
||||||
|
op.value = 'v:' + def.opts[o].v;
|
||||||
|
op.textContent = def.opts[o].label + ' (' + def.opts[o].n + ')';
|
||||||
|
sel.appendChild(op);
|
||||||
|
}
|
||||||
|
var cur = st.sel[def.key] || '';
|
||||||
|
var known = false;
|
||||||
|
if (cur) {
|
||||||
|
for (var c = 0; c < def.opts.length; c++) {
|
||||||
|
if (('v:' + def.opts[c].v) === cur) {
|
||||||
|
known = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!known)
|
||||||
|
cur = '';
|
||||||
|
sel.value = cur;
|
||||||
|
if (cur)
|
||||||
|
keep[def.key] = cur;
|
||||||
|
lab.appendChild(sel);
|
||||||
|
bar.appendChild(lab);
|
||||||
|
}
|
||||||
|
st.sel = keep;
|
||||||
|
if (collected.search) {
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.type = 'search';
|
||||||
|
input.className = 'cbi-input-text';
|
||||||
|
input.placeholder = T('search');
|
||||||
|
input.title = T('searchHint');
|
||||||
|
input.autocomplete = 'off';
|
||||||
|
input.setAttribute('data-pw-search', '1');
|
||||||
|
input.value = st.q;
|
||||||
|
bar.appendChild(input);
|
||||||
|
} else {
|
||||||
|
st.q = '';
|
||||||
|
}
|
||||||
|
var count = document.createElement('span');
|
||||||
|
count.className = 'pw-filter-count';
|
||||||
|
bar.appendChild(count);
|
||||||
|
var reset = document.createElement('button');
|
||||||
|
reset.type = 'button';
|
||||||
|
reset.className = 'btn cbi-button';
|
||||||
|
reset.setAttribute('data-pw-reset', '1');
|
||||||
|
reset.textContent = T('reset');
|
||||||
|
reset.title = T('resetHint');
|
||||||
|
bar.appendChild(reset);
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function listHas(list, value) {
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
if (list[i] === value)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilter(table, collected, bar) {
|
||||||
|
var st = table.__pwF || { sel: {}, q: '' };
|
||||||
|
var q = String(st.q || '').toLowerCase().trim();
|
||||||
|
var active = !!q;
|
||||||
|
var shown = 0;
|
||||||
|
var unchecked = false;
|
||||||
|
var d;
|
||||||
|
for (d = 0; d < collected.defs.length; d++) {
|
||||||
|
if (st.sel[collected.defs[d].key])
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < collected.rows.length; i++) {
|
||||||
|
var tr = collected.rows[i];
|
||||||
|
var ok = true;
|
||||||
|
for (d = 0; d < collected.defs.length; d++) {
|
||||||
|
var sel = st.sel[collected.defs[d].key];
|
||||||
|
if (ok && sel && !listHas(collected.defs[d].vals[i], sel.slice(2)))
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (ok && q && searchText(tr, collected.cols).toLowerCase().indexOf(q) < 0)
|
||||||
|
ok = false;
|
||||||
|
if (ok !== !tr.classList.contains('pw-row-hidden'))
|
||||||
|
tr.classList.toggle('pw-row-hidden', !ok);
|
||||||
|
if (ok) {
|
||||||
|
shown++;
|
||||||
|
var want = 'cbi-rowstyle-' + ((shown - 1) % 2 + 1);
|
||||||
|
var other = want === 'cbi-rowstyle-1' ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1';
|
||||||
|
if (!tr.classList.contains(want))
|
||||||
|
tr.classList.add(want);
|
||||||
|
if (tr.classList.contains(other))
|
||||||
|
tr.classList.remove(other);
|
||||||
|
} else {
|
||||||
|
var cb = tr.querySelector('input.nodes_select');
|
||||||
|
if (cb && cb.checked) {
|
||||||
|
cb.checked = false;
|
||||||
|
unchecked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (unchecked && typeof window.update_select_state === 'function')
|
||||||
|
window.update_select_state();
|
||||||
|
var count = bar.querySelector('.pw-filter-count');
|
||||||
|
var txt = T('shown').replace('%s', shown).replace('%t', collected.rows.length);
|
||||||
|
if (count && count.textContent !== txt)
|
||||||
|
count.textContent = txt;
|
||||||
|
if (bar.classList.contains('pw-filter-on') !== active)
|
||||||
|
bar.classList.toggle('pw-filter-on', active);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBar(table) {
|
||||||
|
var bar = document.getElementById(barId(table));
|
||||||
|
if (bar && bar.parentNode)
|
||||||
|
bar.parentNode.removeChild(bar);
|
||||||
|
var rows = dataRows(table);
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
if (rows[i].classList.contains('pw-row-hidden'))
|
||||||
|
rows[i].classList.remove('pw-row-hidden');
|
||||||
|
}
|
||||||
|
table.__pwSig = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFilter(table) {
|
||||||
|
if (!infoOf(table))
|
||||||
|
return;
|
||||||
|
var collected = collect(table);
|
||||||
|
if (!collected.defs.length && !collected.search) {
|
||||||
|
if (table.__pwSig)
|
||||||
|
removeBar(table);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var bar = document.getElementById(barId(table));
|
||||||
|
if (!bar || collected.sig !== table.__pwSig) {
|
||||||
|
bar = buildBar(table, collected);
|
||||||
|
table.__pwSig = collected.sig;
|
||||||
|
}
|
||||||
|
applyFilter(table, collected, bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function barTable(el) {
|
||||||
|
var bar = el && el.closest ? el.closest('.pw-filter') : null;
|
||||||
|
return bar && bar.__pwTable ? bar.__pwTable : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventTarget(ev) {
|
||||||
|
var t = ev.target;
|
||||||
|
if (t && t.nodeType !== 1)
|
||||||
|
t = t.parentNode;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(root) {
|
||||||
|
root.addEventListener('click', function (ev) {
|
||||||
|
var t = eventTarget(ev);
|
||||||
|
if (t && t.getAttribute && t.getAttribute('data-pw-reset')) {
|
||||||
|
var resetTable = barTable(t);
|
||||||
|
if (!resetTable)
|
||||||
|
return;
|
||||||
|
ev.preventDefault();
|
||||||
|
resetTable.__pwF = { sel: {}, q: '' };
|
||||||
|
resetTable.__pwSig = null;
|
||||||
|
refreshFilter(resetTable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var el = t && t.closest ? t.closest('[data-pw-sort]') : null;
|
||||||
|
if (!el || !root.contains(el))
|
||||||
|
return;
|
||||||
|
if (t.closest && t.closest('input, a, button, select'))
|
||||||
|
return;
|
||||||
|
var th = el.closest('th');
|
||||||
|
var table = el.closest('table');
|
||||||
|
if (!th || !table || !infoOf(table))
|
||||||
|
return;
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
requestSort(table, th, el === th ? null : el, el.getAttribute('data-pw-sort'));
|
||||||
|
});
|
||||||
|
root.addEventListener('change', function (ev) {
|
||||||
|
var t = eventTarget(ev);
|
||||||
|
if (!t || !t.getAttribute || !t.getAttribute('data-pw-filter'))
|
||||||
|
return;
|
||||||
|
var table = barTable(t);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
(table.__pwF = table.__pwF || { sel: {}, q: '' }).sel[t.getAttribute('data-pw-filter')] = t.value;
|
||||||
|
refreshFilter(table);
|
||||||
|
});
|
||||||
|
root.addEventListener('input', function (ev) {
|
||||||
|
var t = eventTarget(ev);
|
||||||
|
if (!t || !t.getAttribute || !t.getAttribute('data-pw-search'))
|
||||||
|
return;
|
||||||
|
var table = barTable(t);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
clearTimeout(searchTimer);
|
||||||
|
searchTimer = setTimeout(function () {
|
||||||
|
(table.__pwF = table.__pwF || { sel: {}, q: '' }).q = t.value;
|
||||||
|
refreshFilter(table);
|
||||||
|
}, 150);
|
||||||
|
});
|
||||||
|
root.addEventListener('keydown', function (ev) {
|
||||||
|
var t = eventTarget(ev);
|
||||||
|
if (ev.key === 'Enter' && t && t.getAttribute && t.getAttribute('data-pw-search'))
|
||||||
|
ev.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var box = document.getElementById('pw2-nodesort-i18n');
|
||||||
|
var root = document.getElementById('node_list');
|
||||||
|
if (!box || !root)
|
||||||
|
return;
|
||||||
|
state.app = box.getAttribute('data-app') || 'passwall2';
|
||||||
|
state.text = readText(box);
|
||||||
|
state.root = root;
|
||||||
|
prepareCollator();
|
||||||
|
if (!state.bound) {
|
||||||
|
bind(root);
|
||||||
|
state.bound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enhanceAll() {
|
||||||
|
if (!state.root)
|
||||||
|
return;
|
||||||
|
var tables = state.root.querySelectorAll('table.cbi-section-table');
|
||||||
|
for (var i = 0; i < tables.length; i++) {
|
||||||
|
try {
|
||||||
|
enhance(tables[i]);
|
||||||
|
refreshFilter(tables[i]);
|
||||||
|
} catch (e) {
|
||||||
|
if (window.console)
|
||||||
|
console.warn('passwall2 node list sort:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.pw2NodeSort = {
|
||||||
|
init: init,
|
||||||
|
enhanceAll: enhanceAll,
|
||||||
|
rowVisible: rowVisible
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -586,11 +586,15 @@ function gen_config_server(node)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if node.protocol == "vmess" or node.protocol == "vless" then
|
if node.protocol == "vless" then
|
||||||
|
settings = {
|
||||||
|
users = users,
|
||||||
|
decryption = (node.decryption and node.decryption ~= "") and node.decryption or "none"
|
||||||
|
}
|
||||||
|
elseif node.protocol == "vmess" then
|
||||||
if users then
|
if users then
|
||||||
settings = {
|
settings = {
|
||||||
users = users,
|
users = users
|
||||||
decryption = (node.protocol == "vless") and ((node.decryption and node.decryption ~= "") and node.decryption or "none") or nil
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
elseif node.protocol == "socks" then
|
elseif node.protocol == "socks" then
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ if node then
|
|||||||
end
|
end
|
||||||
-%>
|
-%>
|
||||||
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js"></script>
|
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js"></script>
|
||||||
|
<script src="<%=resource%>/view/<%=appname%>/node_list_sort.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
table th, .table .th {
|
table th, .table .th {
|
||||||
@@ -383,6 +384,89 @@ table td, .table .td {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* node list: click-to-sort headers and the per-group filter bar */
|
||||||
|
th[data-pw-sort] {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
th[data-pw-sort]:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-sort-arrow {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 1em;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-sort-sub {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 85%;
|
||||||
|
opacity: 0.85;
|
||||||
|
margin-left: 0.6em;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-sort-sub.pw-sort-active {
|
||||||
|
opacity: 1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-sort-note {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
padding: 0.3em 0.6em;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(251, 154, 5, 0.15);
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.pw-row-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3em 0.8em;
|
||||||
|
margin: 0.4em 0;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter select,
|
||||||
|
.pw-filter input[type="search"],
|
||||||
|
.pw-filter .cbi-button {
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 6em;
|
||||||
|
max-width: 22em;
|
||||||
|
height: auto;
|
||||||
|
padding: 0.15em 0.3em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter .pw-filter-count {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pw-filter.pw-filter-on .pw-filter-count {
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
<% if api.is_js_luci() then -%>
|
<% if api.is_js_luci() then -%>
|
||||||
var cbi_t = [];
|
var cbi_t = [];
|
||||||
@@ -538,15 +622,29 @@ table td, .table .td {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function node_row_visible(el) {
|
||||||
|
if (window.pw2NodeSort && typeof pw2NodeSort.rowVisible === "function")
|
||||||
|
return pw2NodeSort.rowVisible(el);
|
||||||
|
var row = el && el.closest ? el.closest("tr") : null;
|
||||||
|
return !row || !row.classList.contains("pw-row-hidden");
|
||||||
|
}
|
||||||
|
|
||||||
function set_select_all_state(sectionChecked) {
|
function set_select_all_state(sectionChecked) {
|
||||||
var visibleContainer = document.querySelector('#cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display: block"]');
|
var visibleContainer = document.querySelector('#cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-<%=appname%>-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||||
if (!visibleContainer) return;
|
if (!visibleContainer) return;
|
||||||
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
||||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||||
var selectAllBtn = document.getElementById("select_all_btn");
|
var selectAllBtn = document.getElementById("select_all_btn");
|
||||||
|
var visibleCount = 0;
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
if (!node_row_visible(nodes[i])) {
|
||||||
|
nodes[i].checked = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
nodes[i].checked = sectionChecked;
|
nodes[i].checked = sectionChecked;
|
||||||
|
visibleCount++;
|
||||||
}
|
}
|
||||||
|
sectionChecked = sectionChecked && visibleCount > 0;
|
||||||
if (selectAllChk) {
|
if (selectAllChk) {
|
||||||
selectAllChk.checked = sectionChecked;
|
selectAllChk.checked = sectionChecked;
|
||||||
selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||||
@@ -574,10 +672,13 @@ table td, .table .td {
|
|||||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||||
var selectAllBtn = document.getElementById("select_all_btn");
|
var selectAllBtn = document.getElementById("select_all_btn");
|
||||||
var checkedCount = 0;
|
var checkedCount = 0;
|
||||||
|
var visibleCount = 0;
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
if (!node_row_visible(nodes[i])) continue;
|
||||||
|
visibleCount++;
|
||||||
if (nodes[i].checked) checkedCount++;
|
if (nodes[i].checked) checkedCount++;
|
||||||
}
|
}
|
||||||
var allChecked = checkedCount === nodes.length;
|
var allChecked = visibleCount > 0 && checkedCount === visibleCount;
|
||||||
var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||||
var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)";
|
var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)";
|
||||||
|
|
||||||
@@ -600,7 +701,7 @@ table td, .table .td {
|
|||||||
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||||
if (doms && doms.length > 0) {
|
if (doms && doms.length > 0) {
|
||||||
for (var i = 0 ; i < doms.length; i++) {
|
for (var i = 0 ; i < doms.length; i++) {
|
||||||
if (doms[i].checked) {
|
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||||
ids.push(doms[i].getAttribute("cbid"))
|
ids.push(doms[i].getAttribute("cbid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1158,7 +1259,7 @@ table td, .table .td {
|
|||||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||||
if (doms && doms.length > 0) {
|
if (doms && doms.length > 0) {
|
||||||
for (let i = 0 ; i < doms.length; i++) {
|
for (let i = 0 ; i < doms.length; i++) {
|
||||||
if (doms[i].checked) {
|
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||||
ids.push(doms[i].getAttribute("cbid"))
|
ids.push(doms[i].getAttribute("cbid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1190,7 +1291,7 @@ table td, .table .td {
|
|||||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||||
if (doms && doms.length > 0) {
|
if (doms && doms.length > 0) {
|
||||||
for (let i = 0 ; i < doms.length; i++) {
|
for (let i = 0 ; i < doms.length; i++) {
|
||||||
if (doms[i].checked) {
|
if (doms[i].checked && node_row_visible(doms[i])) {
|
||||||
ids.push(doms[i].getAttribute("cbid"))
|
ids.push(doms[i].getAttribute("cbid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1298,8 +1399,33 @@ table td, .table .td {
|
|||||||
<fieldset class="cbi-section" id="node_list">
|
<fieldset class="cbi-section" id="node_list">
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<div id="pw2-nodesort-i18n" data-app="<%=appname%>" style="display:none">
|
||||||
|
<span data-k="hint"><%:Click to sort; click again to reverse. Press Save & Apply to keep the order.%></span>
|
||||||
|
<span data-k="typeHint"><%:Sort by type or protocol, then by name%></span>
|
||||||
|
<span data-k="addrHint"><%:Sort by server address and port%></span>
|
||||||
|
<span data-k="origHint"><%:Restore the saved order%></span>
|
||||||
|
<span data-k="changed"><%:The order changed on this page only. Press Save & Apply to store it on the router.%></span>
|
||||||
|
<span data-k="restored"><%:Saved order restored.%></span>
|
||||||
|
<span data-k="wait"><%:Automatic ping is still running (%d left). Sorting will run when it finishes.%></span>
|
||||||
|
<span data-k="waitFail"><%:Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again.%></span>
|
||||||
|
<span data-k="filter"><%:Filter:%></span>
|
||||||
|
<span data-k="all"><%:All%></span>
|
||||||
|
<span data-k="empty"><%:(empty)%></span>
|
||||||
|
<span data-k="type"><%:Type%></span>
|
||||||
|
<span data-k="addr"><%:Address%></span>
|
||||||
|
<span data-k="fWord"><%:Word in remarks%></span>
|
||||||
|
<span data-k="search"><%:search...%></span>
|
||||||
|
<span data-k="searchHint"><%:Show only rows that contain this text%></span>
|
||||||
|
<span data-k="shown"><%:Shown %s of %t%></span>
|
||||||
|
<span data-k="reset"><%:Reset%></span>
|
||||||
|
<span data-k="resetHint"><%:Show all rows%></span>
|
||||||
|
<span data-k="filterHint"><%:The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows.%></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
//init logic
|
//init logic
|
||||||
|
if (window.pw2NodeSort)
|
||||||
|
pw2NodeSort.init();
|
||||||
|
|
||||||
function get_remarks_name(o) {
|
function get_remarks_name(o) {
|
||||||
let str = "";
|
let str = "";
|
||||||
@@ -1499,8 +1625,10 @@ table td, .table .td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_now_use_node();
|
get_now_use_node();
|
||||||
|
|
||||||
pingAllNodes();
|
pingAllNodes();
|
||||||
|
if (window.pw2NodeSort)
|
||||||
|
pw2NodeSort.enhanceAll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2530,3 +2530,54 @@ msgstr "بهروزرسانی ناموفق بود"
|
|||||||
|
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
msgstr "خطای ناشناخته"
|
msgstr "خطای ناشناخته"
|
||||||
|
|
||||||
|
msgid "Click to sort; click again to reverse. Press Save & Apply to keep the order."
|
||||||
|
msgstr "برای مرتبسازی کلیک کنید؛ کلیک دوباره جهت را عوض میکند. برای نگه داشتن ترتیب، ذخیره و اعمال را بزنید."
|
||||||
|
|
||||||
|
msgid "Sort by type or protocol, then by name"
|
||||||
|
msgstr "مرتبسازی بر اساس نوع یا پروتکل، سپس نام"
|
||||||
|
|
||||||
|
msgid "Sort by server address and port"
|
||||||
|
msgstr "مرتبسازی بر اساس آدرس سرور و درگاه"
|
||||||
|
|
||||||
|
msgid "Restore the saved order"
|
||||||
|
msgstr "بازگرداندن ترتیب ذخیرهشده"
|
||||||
|
|
||||||
|
msgid "The order changed on this page only. Press Save & Apply to store it on the router."
|
||||||
|
msgstr "ترتیب فقط در این صفحه عوض شده است. برای نوشتن آن روی روتر، ذخیره و اعمال را بزنید."
|
||||||
|
|
||||||
|
msgid "Saved order restored."
|
||||||
|
msgstr "ترتیب ذخیرهشده بازگردانده شد."
|
||||||
|
|
||||||
|
msgid "Automatic ping is still running (%d left). Sorting will run when it finishes."
|
||||||
|
msgstr "بررسی خودکار هنوز در حال اجراست (%d باقی مانده). مرتبسازی بلافاصله پس از پایان آن انجام میشود."
|
||||||
|
|
||||||
|
msgid "Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again."
|
||||||
|
msgstr "بررسی خودکار ظرف 2 دقیقه تمام نشد. مرتبسازی لغو شد. صفحه را دوباره بارگذاری کنید و دوباره تلاش کنید."
|
||||||
|
|
||||||
|
msgid "Filter:"
|
||||||
|
msgstr "فیلتر:"
|
||||||
|
|
||||||
|
msgid "(empty)"
|
||||||
|
msgstr "(خالی)"
|
||||||
|
|
||||||
|
msgid "Word in remarks"
|
||||||
|
msgstr "کلمه در توضیحات"
|
||||||
|
|
||||||
|
msgid "search..."
|
||||||
|
msgstr "جستجو..."
|
||||||
|
|
||||||
|
msgid "Show only rows that contain this text"
|
||||||
|
msgstr "فقط ردیفهایی را نشان بده که این متن را دارند"
|
||||||
|
|
||||||
|
msgid "Shown %s of %t"
|
||||||
|
msgstr "نمایش %s از %t"
|
||||||
|
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr "بازنشانی"
|
||||||
|
|
||||||
|
msgid "Show all rows"
|
||||||
|
msgstr "نمایش همه ردیفها"
|
||||||
|
|
||||||
|
msgid "The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows."
|
||||||
|
msgstr "فیلتر فقط ردیفها را در این صفحه پنهان میکند و چیزی را روی روتر تغییر نمیدهد. انتخاب همه، ردیفهای پنهان را رد میکند."
|
||||||
@@ -2531,3 +2531,54 @@ msgstr "Ошибка обновления"
|
|||||||
|
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
msgstr "Неизвестная ошибка"
|
msgstr "Неизвестная ошибка"
|
||||||
|
|
||||||
|
msgid "Click to sort; click again to reverse. Press Save & Apply to keep the order."
|
||||||
|
msgstr "Нажмите для сортировки; повторное нажатие меняет направление. Чтобы сохранить порядок, нажмите «Сохранить и применить»."
|
||||||
|
|
||||||
|
msgid "Sort by type or protocol, then by name"
|
||||||
|
msgstr "Сортировать по типу или протоколу, затем по имени"
|
||||||
|
|
||||||
|
msgid "Sort by server address and port"
|
||||||
|
msgstr "Сортировать по адресу сервера и порту"
|
||||||
|
|
||||||
|
msgid "Restore the saved order"
|
||||||
|
msgstr "Вернуть сохранённый порядок"
|
||||||
|
|
||||||
|
msgid "The order changed on this page only. Press Save & Apply to store it on the router."
|
||||||
|
msgstr "Порядок изменён только на этой странице. Чтобы записать его на роутер, нажмите «Сохранить и применить»."
|
||||||
|
|
||||||
|
msgid "Saved order restored."
|
||||||
|
msgstr "Сохранённый порядок восстановлен."
|
||||||
|
|
||||||
|
msgid "Automatic ping is still running (%d left). Sorting will run when it finishes."
|
||||||
|
msgstr "Автопроверка ещё выполняется (осталось %d). Сортировка запустится сразу после её окончания."
|
||||||
|
|
||||||
|
msgid "Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again."
|
||||||
|
msgstr "Автопроверка не завершилась за 2 минуты. Сортировка отменена. Обновите страницу и попробуйте снова."
|
||||||
|
|
||||||
|
msgid "Filter:"
|
||||||
|
msgstr "Фильтр:"
|
||||||
|
|
||||||
|
msgid "(empty)"
|
||||||
|
msgstr "(пусто)"
|
||||||
|
|
||||||
|
msgid "Word in remarks"
|
||||||
|
msgstr "Слово в описании"
|
||||||
|
|
||||||
|
msgid "search..."
|
||||||
|
msgstr "поиск..."
|
||||||
|
|
||||||
|
msgid "Show only rows that contain this text"
|
||||||
|
msgstr "Показать только строки, содержащие этот текст"
|
||||||
|
|
||||||
|
msgid "Shown %s of %t"
|
||||||
|
msgstr "Показано %s из %t"
|
||||||
|
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr "Сбросить"
|
||||||
|
|
||||||
|
msgid "Show all rows"
|
||||||
|
msgstr "Показать все строки"
|
||||||
|
|
||||||
|
msgid "The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows."
|
||||||
|
msgstr "Фильтр только скрывает строки на этой странице и ничего не меняет на роутере. «Выделить все» пропускает скрытые строки."
|
||||||
@@ -2517,3 +2517,53 @@ msgstr "更新失败"
|
|||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
msgstr "未知错误"
|
msgstr "未知错误"
|
||||||
|
|
||||||
|
msgid "Click to sort; click again to reverse. Press Save & Apply to keep the order."
|
||||||
|
msgstr "点击表头排序,再次点击切换方向。按保存并应用可保留顺序。"
|
||||||
|
|
||||||
|
msgid "Sort by type or protocol, then by name"
|
||||||
|
msgstr "先按类型或协议排序,再按名称排序"
|
||||||
|
|
||||||
|
msgid "Sort by server address and port"
|
||||||
|
msgstr "按服务器地址和端口排序"
|
||||||
|
|
||||||
|
msgid "Restore the saved order"
|
||||||
|
msgstr "恢复已保存的顺序"
|
||||||
|
|
||||||
|
msgid "The order changed on this page only. Press Save & Apply to store it on the router."
|
||||||
|
msgstr "顺序仅在本页改变。按保存并应用才会写入路由器。"
|
||||||
|
|
||||||
|
msgid "Saved order restored."
|
||||||
|
msgstr "已恢复保存的顺序。"
|
||||||
|
|
||||||
|
msgid "Automatic ping is still running (%d left). Sorting will run when it finishes."
|
||||||
|
msgstr "自动探测仍在进行(剩余 %d)。结束后会立即排序。"
|
||||||
|
|
||||||
|
msgid "Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again."
|
||||||
|
msgstr "自动探测在 2 分钟内未结束,已取消排序。请刷新页面后重试。"
|
||||||
|
|
||||||
|
msgid "Filter:"
|
||||||
|
msgstr "筛选:"
|
||||||
|
|
||||||
|
msgid "(empty)"
|
||||||
|
msgstr "(空)"
|
||||||
|
|
||||||
|
msgid "Word in remarks"
|
||||||
|
msgstr "备注关键词"
|
||||||
|
|
||||||
|
msgid "search..."
|
||||||
|
msgstr "搜索..."
|
||||||
|
|
||||||
|
msgid "Show only rows that contain this text"
|
||||||
|
msgstr "只显示包含此文本的行"
|
||||||
|
|
||||||
|
msgid "Shown %s of %t"
|
||||||
|
msgstr "显示 %s / %t"
|
||||||
|
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr "重置"
|
||||||
|
|
||||||
|
msgid "Show all rows"
|
||||||
|
msgstr "显示全部行"
|
||||||
|
|
||||||
|
msgid "The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows."
|
||||||
|
msgstr "筛选只在本页隐藏行,不会改动路由器上的设置。全选会跳过隐藏行。"
|
||||||
@@ -2522,3 +2522,54 @@ msgstr "更新失敗"
|
|||||||
|
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
msgstr "未知錯誤"
|
msgstr "未知錯誤"
|
||||||
|
|
||||||
|
msgid "Click to sort; click again to reverse. Press Save & Apply to keep the order."
|
||||||
|
msgstr "點擊表頭排序,再次點擊切換方向。按儲存並套用可保留順序。"
|
||||||
|
|
||||||
|
msgid "Sort by type or protocol, then by name"
|
||||||
|
msgstr "先按類型或協定排序,再按名稱排序"
|
||||||
|
|
||||||
|
msgid "Sort by server address and port"
|
||||||
|
msgstr "按伺服器位址和埠排序"
|
||||||
|
|
||||||
|
msgid "Restore the saved order"
|
||||||
|
msgstr "還原已儲存的順序"
|
||||||
|
|
||||||
|
msgid "The order changed on this page only. Press Save & Apply to store it on the router."
|
||||||
|
msgstr "順序只在本頁改變。按儲存並套用才會寫入路由器。"
|
||||||
|
|
||||||
|
msgid "Saved order restored."
|
||||||
|
msgstr "已還原儲存的順序。"
|
||||||
|
|
||||||
|
msgid "Automatic ping is still running (%d left). Sorting will run when it finishes."
|
||||||
|
msgstr "自動探測仍在進行(剩餘 %d)。結束後會立即排序。"
|
||||||
|
|
||||||
|
msgid "Automatic ping did not finish within 2 minutes. Sorting was cancelled. Reload the page and try again."
|
||||||
|
msgstr "自動探測在 2 分鐘內未結束,已取消排序。請重新整理頁面後再試。"
|
||||||
|
|
||||||
|
msgid "Filter:"
|
||||||
|
msgstr "篩選:"
|
||||||
|
|
||||||
|
msgid "(empty)"
|
||||||
|
msgstr "(空)"
|
||||||
|
|
||||||
|
msgid "Word in remarks"
|
||||||
|
msgstr "備註關鍵詞"
|
||||||
|
|
||||||
|
msgid "search..."
|
||||||
|
msgstr "搜尋..."
|
||||||
|
|
||||||
|
msgid "Show only rows that contain this text"
|
||||||
|
msgstr "只顯示包含此文字的列"
|
||||||
|
|
||||||
|
msgid "Shown %s of %t"
|
||||||
|
msgstr "顯示 %s / %t"
|
||||||
|
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr "重設"
|
||||||
|
|
||||||
|
msgid "Show all rows"
|
||||||
|
msgstr "顯示全部列"
|
||||||
|
|
||||||
|
msgid "The filter only hides rows on this page and changes nothing on the router. Select all skips hidden rows."
|
||||||
|
msgstr "篩選只在本頁隱藏列,不會改動路由器上的設定。全選會跳過隱藏列。"
|
||||||
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
|
|||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
PKG_NAME:=luci-app-ssr-plus
|
PKG_NAME:=luci-app-ssr-plus
|
||||||
PKG_VERSION:=198
|
PKG_VERSION:=198
|
||||||
PKG_RELEASE:=96
|
PKG_RELEASE:=97
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||||
|
|||||||
@@ -716,6 +716,10 @@ 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.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||||
o:depends("hysteria2_realms", true)
|
o:depends("hysteria2_realms", true)
|
||||||
|
|
||||||
|
o = s:option(Flag, "hysteria2_realm_upnp", translate("Enable UPnP/NAT-PMP"), translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
|
||||||
|
o.default = "0"
|
||||||
|
o:depends("hysteria2_realms", true)
|
||||||
|
|
||||||
o = s:option(Value, "hy2_auth", translate("Users Authentication"))
|
o = s:option(Value, "hy2_auth", translate("Users Authentication"))
|
||||||
o:depends("type", "hysteria2")
|
o:depends("type", "hysteria2")
|
||||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||||||
|
|||||||
@@ -3900,3 +3900,9 @@ msgstr "Naive ECH 配置列表(Base64)"
|
|||||||
|
|
||||||
msgid "Naive transport"
|
msgid "Naive transport"
|
||||||
msgstr "Naive 传输协议"
|
msgstr "Naive 传输协议"
|
||||||
|
|
||||||
|
msgid "Enable UPnP/NAT-PMP"
|
||||||
|
msgstr "启用 UPnP/NAT-PMP"
|
||||||
|
|
||||||
|
msgid "Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."
|
||||||
|
msgstr "在网关上启用 UPnP/NAT-PMP 端口映射以提升打洞成功率。"
|
||||||
@@ -253,13 +253,19 @@ force_dns2tcp_fallback() {
|
|||||||
|
|
||||||
is_builtin_dns_active() {
|
is_builtin_dns_active() {
|
||||||
local global_server_type
|
local global_server_type
|
||||||
|
local enable_mihomo
|
||||||
global_server_type="$(uci_get_by_name "$GLOBAL_SERVER" type)"
|
global_server_type="$(uci_get_by_name "$GLOBAL_SERVER" type)"
|
||||||
case "$global_server_type" in
|
case "$global_server_type" in
|
||||||
clash|tuic|ss)
|
clash|tuic|ss)
|
||||||
ps_list | grep -v "grep" | grep -q "ssr-retcp"
|
ps_list | grep -v "grep" | grep -q "ssr-retcp"
|
||||||
;;
|
;;
|
||||||
v2ray)
|
v2ray)
|
||||||
ps_list | grep -v "grep" | grep -Eq "$TMP_PATH/.*ssr-retcp\\.(json|yaml)"
|
enable_mihomo="$(uci_get_by_type server_subscribe enable_mihomo "")"
|
||||||
|
if [ "$enable_mihomo" = "1" ]; then
|
||||||
|
ps_list | grep -v "grep" | grep -q "ssr-retcp.*v2ray-*"
|
||||||
|
else
|
||||||
|
ps_list | grep -v "grep" | grep -Eq "$TMP_PATH/.*ssr-retcp\\.(json|yaml)"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -722,21 +722,39 @@ Xray.outbounds = {
|
|||||||
local PT = server.v2ray_protocol
|
local PT = server.v2ray_protocol
|
||||||
local TP = server.transport
|
local TP = server.transport
|
||||||
if TP == "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 = {}
|
local udp = {}
|
||||||
if server.kcp_guise and server.kcp_guise ~= "none" then
|
if xray_version_val < 260601 then
|
||||||
local g = { type = map[server.kcp_guise] }
|
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||||
if server.kcp_guise == "dns" and server.kcp_domain and server.kcp_domain ~= "" then
|
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||||
g.settings = { domain = server.kcp_domain }
|
if server.kcp_guise and server.kcp_guise ~= "none" then
|
||||||
|
local g = { type = map[server.kcp_guise] }
|
||||||
|
if server.kcp_guise == "dns" and server.kcp_domain and server.kcp_domain ~= "" then
|
||||||
|
g.settings = { domain = server.kcp_domain }
|
||||||
|
end
|
||||||
|
udp[#udp+1] = g
|
||||||
end
|
end
|
||||||
udp[#udp+1] = g
|
local c = { type = (server.seed and server.seed ~= "") and "mkcp-aes128gcm" or "mkcp-original" }
|
||||||
|
if server.seed and server.seed ~= "" then
|
||||||
|
c.settings = { password = server.seed }
|
||||||
|
end
|
||||||
|
udp[#udp+1] = c
|
||||||
|
else
|
||||||
|
local map = {none = "none", srtp = "srtp", utp = "utp", ["wechat-video"] = "wechat",
|
||||||
|
dtls = "dtls", wireguard = "wireguard", dns = "dns"}
|
||||||
|
if server.kcp_guise and server.kcp_guise ~= "none" then
|
||||||
|
local g = { type = "mkcp-legacy" }
|
||||||
|
g.settings = { header = map[server.kcp_guise] }
|
||||||
|
if server.kcp_guise == "dns" and server.kcp_domain and server.kcp_domain ~= "" then
|
||||||
|
g.settings.value = server.kcp_domain
|
||||||
|
end
|
||||||
|
udp[#udp+1] = g
|
||||||
|
end
|
||||||
|
local s = { type = "mkcp-legacy" }
|
||||||
|
if server.seed and server.seed ~= "" then
|
||||||
|
s.settings = { value = server.seed }
|
||||||
|
end
|
||||||
|
udp[#udp+1] = s
|
||||||
end
|
end
|
||||||
local c = { type = (server.seed and server.seed ~= "") and "mkcp-aes128gcm" or "mkcp-original" }
|
|
||||||
if server.seed and server.seed ~= "" then
|
|
||||||
c.settings = { password = server.seed }
|
|
||||||
end
|
|
||||||
udp[#udp+1] = c
|
|
||||||
finalmask.udp = udp
|
finalmask.udp = udp
|
||||||
elseif PT == "hysteria2" then
|
elseif PT == "hysteria2" then
|
||||||
local udp = {}
|
local udp = {}
|
||||||
@@ -769,7 +787,8 @@ Xray.outbounds = {
|
|||||||
type = "realm",
|
type = "realm",
|
||||||
settings = {
|
settings = {
|
||||||
url = url,
|
url = url,
|
||||||
stunServers = stun
|
stunServers = stun,
|
||||||
|
portMapping = (node.hysteria2_realm_upnp == "1") and { enabled = true } or nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
udp[#udp+1] = r
|
udp[#udp+1] = r
|
||||||
@@ -782,7 +801,7 @@ Xray.outbounds = {
|
|||||||
brutalUp = up > 0 and (up .. "mbps") or nil,
|
brutalUp = up > 0 and (up .. "mbps") or nil,
|
||||||
brutalDown = down > 0 and (down .. "mbps") or nil,
|
brutalDown = down > 0 and (down .. "mbps") or nil,
|
||||||
udpHop = (server.flag_port_hopping == "1") and {
|
udpHop = (server.flag_port_hopping == "1") and {
|
||||||
ports = string.gsub(server.port_range, ":", "-"),
|
ports = server.port_range and string.gsub(server.port_range, ":", "-"),
|
||||||
interval = (function(v)
|
interval = (function(v)
|
||||||
if not v then return 30 end
|
if not v then return 30 end
|
||||||
if v:find("-", 1, true) then
|
if v:find("-", 1, true) then
|
||||||
|
|||||||
@@ -2,6 +2,44 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [2.7.3-r3] - 安装命令改用通配文件名,翻译包并入对应包管理器代码块(仅文档)
|
||||||
|
|
||||||
|
`README.md` / `README.en.md` 的安装步骤不再写死版本号:opkg 用
|
||||||
|
`luci-app-substore_*.ipk`,apk 用 `luci-app-substore-*.apk`,避免每次升
|
||||||
|
PKG_RELEASE 后文档里的文件名立刻过期。简体中文翻译包的安装命令移入同一代码块,
|
||||||
|
用注释标明「中文界面需额外安装翻译包」;原先单独的翻译包安装代码块随之删除,
|
||||||
|
只保留 `PKG_PO_VERSION` 的说明。
|
||||||
|
|
||||||
|
### 影响
|
||||||
|
|
||||||
|
**仅文档,无代码、无测试变更。** PKG_RELEASE 按版本约定 `2.7.3-r2` → `2.7.3-r3`,
|
||||||
|
README 与 `docs/INSTALL.md` 的版本号同步更新。全套 **59 个测试文件、179 条断言、
|
||||||
|
0 失败**(未改动被测代码,跑一遍确认无意外)。
|
||||||
|
|
||||||
|
## [2.7.3-r2] - README 章节重排:安装提前,构建并入「从源码编译」(仅文档)
|
||||||
|
|
||||||
|
`README.md` / `README.en.md` 的「安装」整体移到「功能特性」之前,让拿到包的人先看到
|
||||||
|
安装方式、再看能力清单;「安装」末尾新增 **从源码编译** 小节,原先独立成节的
|
||||||
|
「构建」并入其中并删除。
|
||||||
|
|
||||||
|
合并时修正了原「构建」段里一条**不存在的 make 目标**:
|
||||||
|
`make package/luci-i18n-substore-zh-cn/compile` —— make 的目标按目录生成、不按包名,
|
||||||
|
`.github/workflows/build.yml` 与 `docs/BUILD.md` 均已写明这一点。新小节改为经 CI
|
||||||
|
验证过的做法:翻译包与主包同属 `package/luci-app-substore`,随同一条 `compile`
|
||||||
|
命令产出;它是 HIDDEN 包(无 Kconfig prompt),编译前需显式打开语言符号:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo "CONFIG_LUCI_LANG_zh_Hans=y" >> .config
|
||||||
|
make defconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
同时把 README 与 `docs/INSTALL.md` 中滞后的版本号 `2.7.2-r1` 同步为 `2.7.3-r2`。
|
||||||
|
|
||||||
|
### 影响
|
||||||
|
|
||||||
|
**仅文档,无代码、无测试变更。** PKG_RELEASE 按版本约定 `2.7.3-r1` → `-r2`。全套
|
||||||
|
**59 个测试文件、179 条断言、0 失败**(未改动被测代码,跑一遍确认无意外)。
|
||||||
|
|
||||||
## [2.7.3-r1] - 结清「附:P2 修复范围」:改写为历史范围定义(仅文档)
|
## [2.7.3-r1] - 结清「附:P2 修复范围」:改写为历史范围定义(仅文档)
|
||||||
|
|
||||||
`docs/LEGACY_ISSUES.md` 末尾的「附:P2 修复范围」一节,是 P2 **开工时**写下的范围
|
`docs/LEGACY_ISSUES.md` 末尾的「附:P2 修复范围」一节,是 P2 **开工时**写下的范围
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ PKG_NAME:=luci-app-substore
|
|||||||
# 版本约定:每次提交 PKG_RELEASE +1(2.1.2-r1 ~ r9);
|
# 版本约定:每次提交 PKG_RELEASE +1(2.1.2-r1 ~ r9);
|
||||||
# 达到 r10 时 PKG_VERSION 末位 +1(2.1.2 -> 2.1.3),PKG_RELEASE 重置为 1。
|
# 达到 r10 时 PKG_VERSION 末位 +1(2.1.2 -> 2.1.3),PKG_RELEASE 重置为 1。
|
||||||
PKG_VERSION:=2.7.3
|
PKG_VERSION:=2.7.3
|
||||||
PKG_RELEASE:=24
|
PKG_RELEASE:=25
|
||||||
|
|
||||||
LUCI_DEPENDS:=+luci-lua-runtime +luci-compat
|
LUCI_DEPENDS:=+luci-lua-runtime +luci-compat
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,67 @@ group them, then re-emit them in a format your client can consume.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
> The version in the package name must match `PKG_VERSION` / `PKG_RELEASE` in the
|
||||||
|
> [Makefile](Makefile) (currently `2.7.3-r3`).
|
||||||
|
|
||||||
|
**Minimum supported: OpenWrt / ImmortalWrt 23.05** (older releases are out of scope).
|
||||||
|
|
||||||
|
opkg (OpenWrt / ImmortalWrt 24.10 and earlier):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opkg install luci-app-substore_*.ipk
|
||||||
|
# Simplified Chinese UI needs the translation package as well
|
||||||
|
opkg install luci-i18n-substore-zh-cn_*.ipk
|
||||||
|
```
|
||||||
|
|
||||||
|
OpenWrt 25.12 and newer (apk):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apk add --allow-untrusted luci-app-substore-*.apk
|
||||||
|
# Simplified Chinese UI needs the translation package as well
|
||||||
|
apk add --allow-untrusted luci-i18n-substore-zh-cn-*.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
> The main package ships no translations, so the UI stays English without the
|
||||||
|
> translation package. Its version comes from the `po/` commit time (LuCI's
|
||||||
|
> `PKG_PO_VERSION`), so it does not share the main package's version; install it by
|
||||||
|
> filename wildcard. Set the LuCI language to Simplified Chinese afterwards — the
|
||||||
|
> package's uci-defaults adds it to `luci.languages` automatically.
|
||||||
|
|
||||||
|
Then open LuCI: **Services → Subscriptions**.
|
||||||
|
|
||||||
|
### Building from source
|
||||||
|
|
||||||
|
In an OpenWrt / ImmortalWrt source tree matching the target firmware:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd openwrt
|
||||||
|
./scripts/feeds update -a
|
||||||
|
./scripts/feeds install -a
|
||||||
|
git clone https://github.com/Arthur97172/luci-app-substore.git package/luci-app-substore
|
||||||
|
make menuconfig # LuCI → Applications → luci-app-substore
|
||||||
|
make package/luci-app-substore/compile V=s
|
||||||
|
```
|
||||||
|
|
||||||
|
The Simplified Chinese package `luci-i18n-substore-zh-cn` is generated by `luci.mk`
|
||||||
|
from `po/zh_Hans/` and lives in the same `package/luci-app-substore` directory, so the
|
||||||
|
same `compile` command produces it. It is a HIDDEN package (no Kconfig prompt), though,
|
||||||
|
so enable its language symbol before building or it will not be selected:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo "CONFIG_LUCI_LANG_zh_Hans=y" >> .config
|
||||||
|
make defconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note that `make package/luci-i18n-substore-zh-cn/compile` is **not a valid target** —
|
||||||
|
> make targets are per directory, not per package name (see
|
||||||
|
> [docs/BUILD.md](docs/BUILD.md)).
|
||||||
|
|
||||||
|
The `.ipk` (or `.apk` on apk builds) is produced under `bin/packages/.../`; install
|
||||||
|
it as described above.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
**Subscription management**
|
**Subscription management**
|
||||||
@@ -85,40 +146,6 @@ group them, then re-emit them in a format your client can consume.
|
|||||||
**LuCI interface & i18n**
|
**LuCI interface & i18n**
|
||||||
- English by default, Simplified Chinese when the runtime language is `zh-cn`
|
- English by default, Simplified Chinese when the runtime language is `zh-cn`
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
> The version in the package name must match `PKG_VERSION` / `PKG_RELEASE` in the
|
|
||||||
> [Makefile](Makefile) (currently `2.7.2-r1`).
|
|
||||||
|
|
||||||
**Minimum supported: OpenWrt / ImmortalWrt 23.05** (older releases are out of scope).
|
|
||||||
|
|
||||||
opkg (OpenWrt / ImmortalWrt 24.10 and earlier):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
opkg install luci-app-substore-2.7.2-r1.ipk
|
|
||||||
```
|
|
||||||
|
|
||||||
apk (OpenWrt / ImmortalWrt 25.12+):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apk add --allow-untrusted luci-app-substore-2.7.2-r1.apk
|
|
||||||
```
|
|
||||||
|
|
||||||
The Simplified Chinese UI needs a **separately installed** translation package —
|
|
||||||
the main package ships no translations, so the UI stays English without it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
opkg install luci-i18n-substore-zh-cn_*.ipk # 24.10 and earlier
|
|
||||||
apk add --allow-untrusted luci-i18n-substore-zh-cn_*.apk # 25.12+
|
|
||||||
```
|
|
||||||
|
|
||||||
> Its version comes from the `po/` commit time (LuCI's `PKG_PO_VERSION`), so it
|
|
||||||
> does not share the main package's `2.7.2-r1`; install it by filename wildcard.
|
|
||||||
> Set the LuCI language to Simplified Chinese afterwards — the package's
|
|
||||||
> uci-defaults adds it to `luci.languages` automatically.
|
|
||||||
|
|
||||||
Then open LuCI: **Services → Subscriptions**.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. **Add a subscription** — paste the subscription URL; optionally set up a
|
1. **Add a subscription** — paste the subscription URL; optionally set up a
|
||||||
@@ -208,21 +235,6 @@ oversight; the reasoning for each is in
|
|||||||
- [CHANGELOG.md](CHANGELOG.md) — changelog
|
- [CHANGELOG.md](CHANGELOG.md) — changelog
|
||||||
- [docs/LEGACY_ISSUES.md](docs/LEGACY_ISSUES.md) — known unfixed issues (pending decision)
|
- [docs/LEGACY_ISSUES.md](docs/LEGACY_ISSUES.md) — known unfixed issues (pending decision)
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
Place the package under an OpenWrt / ImmortalWrt SDK or source tree matching the
|
|
||||||
target firmware, then:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp -r luci-app-substore <openwrt-tree>/package/
|
|
||||||
make package/luci-app-substore/compile V=s
|
|
||||||
# Simplified Chinese is a separate package (luci.mk auto-generates it from
|
|
||||||
# po/zh_Hans/), so it must be compiled on its own:
|
|
||||||
make package/luci-i18n-substore-zh-cn/compile V=s
|
|
||||||
```
|
|
||||||
|
|
||||||
The `.ipk` (or `.apk` on apk builds) is produced under `bin/packages/.../`.
|
|
||||||
|
|
||||||
## Development & testing
|
## Development & testing
|
||||||
|
|
||||||
The core is plain Lua 5.1 with no `luci.*` dependency, so it is unit-testable
|
The core is plain Lua 5.1 with no `luci.*` dependency, so it is unit-testable
|
||||||
|
|||||||
+56
-45
@@ -10,6 +10,62 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
> 包名中的版本号必须与 [Makefile](Makefile) 的 `PKG_VERSION` / `PKG_RELEASE` 保持一致
|
||||||
|
> (当前 `2.7.3-r3`)。
|
||||||
|
|
||||||
|
**最低支持 OpenWrt / ImmortalWrt 23.05**(更早的版本不在支持范围内)。
|
||||||
|
|
||||||
|
opkg(OpenWrt / ImmortalWrt 24.10 及更早):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opkg install luci-app-substore_*.ipk
|
||||||
|
# 中文界面需额外安装翻译包
|
||||||
|
opkg install luci-i18n-substore-zh-cn_*.ipk
|
||||||
|
```
|
||||||
|
|
||||||
|
OpenWrt 25.12 及更新版本(apk):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apk add --allow-untrusted luci-app-substore-*.apk
|
||||||
|
# 中文界面需额外安装翻译包
|
||||||
|
apk add --allow-untrusted luci-i18n-substore-zh-cn-*.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
> 主包不含译文,未装翻译包时界面为英文。翻译包的版本号由 `po/` 目录的提交时间推导
|
||||||
|
> (LuCI 的 `PKG_PO_VERSION`),与主包不同名,按文件名通配安装即可。装好后 LuCI
|
||||||
|
> 语言设为「简体中文」即生效(该包的 uci-defaults 会自动把它加进 `luci.languages`)。
|
||||||
|
|
||||||
|
然后在 LuCI 菜单打开:**服务 → 订阅**。
|
||||||
|
|
||||||
|
### 从源码编译
|
||||||
|
|
||||||
|
在版本与目标固件匹配的 OpenWrt / ImmortalWrt 源码树中:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd openwrt
|
||||||
|
./scripts/feeds update -a
|
||||||
|
./scripts/feeds install -a
|
||||||
|
git clone https://github.com/Arthur97172/luci-app-substore.git package/luci-app-substore
|
||||||
|
make menuconfig # LuCI → Applications → luci-app-substore
|
||||||
|
make package/luci-app-substore/compile V=s
|
||||||
|
```
|
||||||
|
|
||||||
|
简体中文翻译包 `luci-i18n-substore-zh-cn` 由 `luci.mk` 按 `po/zh_Hans/` 自动生成,
|
||||||
|
与主包同属 `package/luci-app-substore` 目录,随同一条 `compile` 命令一并产出。但它是
|
||||||
|
HIDDEN 包(无 Kconfig prompt),编译前要显式打开语言符号,否则不会被选中:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo "CONFIG_LUCI_LANG_zh_Hans=y" >> .config
|
||||||
|
make defconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
> 注意**不存在** `make package/luci-i18n-substore-zh-cn/compile` 这个目标 —— make 的
|
||||||
|
> 目标按目录生成,不按包名(详见 [docs/BUILD.md](docs/BUILD.md))。
|
||||||
|
|
||||||
|
`.ipk`(或 apk 构建下的 `.apk`)生成于 `bin/packages/.../` 下,按上面的方式安装即可。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
**订阅管理**
|
**订阅管理**
|
||||||
@@ -72,38 +128,6 @@
|
|||||||
**LuCI 界面与国际化**
|
**LuCI 界面与国际化**
|
||||||
- 默认英文,运行时语言为 `zh-cn` 时自动显示简体中文
|
- 默认英文,运行时语言为 `zh-cn` 时自动显示简体中文
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
> 包名中的版本号必须与 [Makefile](Makefile) 的 `PKG_VERSION` / `PKG_RELEASE` 保持一致
|
|
||||||
> (当前 `2.7.2-r1`)。
|
|
||||||
|
|
||||||
**最低支持 OpenWrt / ImmortalWrt 23.05**(更早的版本不在支持范围内)。
|
|
||||||
|
|
||||||
opkg(OpenWrt / ImmortalWrt 24.10 及更早):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
opkg install luci-app-substore-2.7.2-r1.ipk
|
|
||||||
```
|
|
||||||
|
|
||||||
apk(OpenWrt / ImmortalWrt 25.12+):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apk add --allow-untrusted luci-app-substore-2.7.2-r1.apk
|
|
||||||
```
|
|
||||||
|
|
||||||
简体中文界面需要**单独安装**翻译包 —— 主包不含译文,未装时界面为英文:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
opkg install luci-i18n-substore-zh-cn_*.ipk # 24.10 及更早
|
|
||||||
apk add --allow-untrusted luci-i18n-substore-zh-cn_*.apk # 25.12+
|
|
||||||
```
|
|
||||||
|
|
||||||
> 翻译包的版本号由 `po/` 目录的提交时间推导(LuCI 的 `PKG_PO_VERSION`),
|
|
||||||
> 与主包的 `2.7.2-r1` 不同名,按文件名通配安装即可。装好后 LuCI 语言设为
|
|
||||||
> 「简体中文」即生效(该包的 uci-defaults 会自动把它加进 `luci.languages`)。
|
|
||||||
|
|
||||||
然后在 LuCI 菜单打开:**服务 → 订阅**。
|
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
1. **添加订阅** —— 粘贴订阅 URL;可选的按订阅 cron 定时、规则或下载代理。
|
1. **添加订阅** —— 粘贴订阅 URL;可选的按订阅 cron 定时、规则或下载代理。
|
||||||
@@ -180,19 +204,6 @@ apk add --allow-untrusted luci-i18n-substore-zh-cn_*.apk # 25.12+
|
|||||||
- [CHANGELOG.md](CHANGELOG.md) — 更新日志
|
- [CHANGELOG.md](CHANGELOG.md) — 更新日志
|
||||||
- [docs/LEGACY_ISSUES.md](docs/LEGACY_ISSUES.md) — 遗留缺陷汇总(待决定修复方案)
|
- [docs/LEGACY_ISSUES.md](docs/LEGACY_ISSUES.md) — 遗留缺陷汇总(待决定修复方案)
|
||||||
|
|
||||||
## 构建
|
|
||||||
|
|
||||||
将本包放入与目标固件版本匹配的 OpenWrt / ImmortalWrt SDK 或源码树:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp -r luci-app-substore <openwrt-tree>/package/
|
|
||||||
make package/luci-app-substore/compile V=s
|
|
||||||
# 简体中文翻译是独立包(luci.mk 按 po/zh_Hans/ 自动生成),需要单独编译:
|
|
||||||
make package/luci-i18n-substore-zh-cn/compile V=s
|
|
||||||
```
|
|
||||||
|
|
||||||
`.ipk`(或 apk 构建下的 `.apk`)生成于 `bin/packages/.../` 下。
|
|
||||||
|
|
||||||
## 开发与测试
|
## 开发与测试
|
||||||
|
|
||||||
核心逻辑为纯 Lua 5.1,不依赖 `luci.*`,无需设备即可单元测试。`tests/` 下每个测试文件自包含:
|
核心逻辑为纯 Lua 5.1,不依赖 `luci.*`,无需设备即可单元测试。`tests/` 下每个测试文件自包含:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
> NOTE: stage-0 skeleton has not yet been installed/verified on a target device.
|
> NOTE: stage-0 skeleton has not yet been installed/verified on a target device.
|
||||||
> This document will be updated once installation is validated.
|
> This document will be updated once installation is validated.
|
||||||
>
|
>
|
||||||
> 包名中的版本号需与 Makefile 的 `PKG_VERSION` / `PKG_RELEASE` 保持同步(当前 2.7.2-r1)。
|
> 包名中的版本号需与 Makefile 的 `PKG_VERSION` / `PKG_RELEASE` 保持同步(当前 2.7.3-r3)。
|
||||||
|
|
||||||
**最低支持 OpenWrt / ImmortalWrt 23.05**(更早的版本不在支持范围内 —— 23.05 起
|
**最低支持 OpenWrt / ImmortalWrt 23.05**(更早的版本不在支持范围内 —— 23.05 起
|
||||||
LuCI 使用 ucode dispatcher,ACL 的入口级校验依赖它,见
|
LuCI 使用 ucode dispatcher,ACL 的入口级校验依赖它,见
|
||||||
@@ -12,13 +12,13 @@ LuCI 使用 ucode dispatcher,ACL 的入口级校验依赖它,见
|
|||||||
## Install (opkg — OpenWrt / ImmortalWrt 24.10 及更早)
|
## Install (opkg — OpenWrt / ImmortalWrt 24.10 及更早)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
opkg install luci-app-substore-2.7.2-r1.ipk
|
opkg install luci-app-substore-2.7.3-r3.ipk
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install (apk — OpenWrt / ImmortalWrt 25.12+)
|
## Install (apk — OpenWrt / ImmortalWrt 25.12+)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
apk add --allow-untrusted luci-app-substore-2.7.2-r1.apk
|
apk add --allow-untrusted luci-app-substore-2.7.3-r3.apk
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install the Simplified Chinese translation (optional, separate package)
|
## Install the Simplified Chinese translation (optional, separate package)
|
||||||
@@ -32,7 +32,7 @@ apk add --allow-untrusted luci-i18n-substore-zh-cn_*.apk # 25.12+
|
|||||||
```
|
```
|
||||||
|
|
||||||
该包的版本号由 `po/` 目录的提交时间推导(LuCI 的 `PKG_PO_VERSION`),
|
该包的版本号由 `po/` 目录的提交时间推导(LuCI 的 `PKG_PO_VERSION`),
|
||||||
与主包的 `2.7.2-r1` 不同名,按文件名通配安装即可。它会安装
|
与主包的 `2.7.3-r3` 不同名,按文件名通配安装即可。它会安装
|
||||||
`/usr/lib/lua/luci/i18n/substore.zh-cn.lmo`,并通过 uci-defaults 把
|
`/usr/lib/lua/luci/i18n/substore.zh-cn.lmo`,并通过 uci-defaults 把
|
||||||
`zh_cn` 加进 `luci.languages`;把 LuCI 语言切到「简体中文」即生效。
|
`zh_cn` 加进 `luci.languages`;把 LuCI 语言切到「简体中文」即生效。
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,42 @@ const callDoLogout = rpc.declare({ object: 'tailscale', method: 'do_logout' });
|
|||||||
const callGetSubroutes = rpc.declare({ object: 'tailscale', method: 'get_subroutes' });
|
const callGetSubroutes = rpc.declare({ object: 'tailscale', method: 'get_subroutes' });
|
||||||
const callSetupFirewall = rpc.declare({ object: 'tailscale', method: 'setup_firewall' });
|
const callSetupFirewall = rpc.declare({ object: 'tailscale', method: 'setup_firewall' });
|
||||||
const callGetLogs = rpc.declare({ object: 'tailscale', method: 'get_logs' });
|
const callGetLogs = rpc.declare({ object: 'tailscale', method: 'get_logs' });
|
||||||
|
const callSetServiceEnabled = rpc.declare({ object: 'tailscale', method: 'set_service_enabled', params: ['enabled'] });
|
||||||
|
const callGetFirewallStatus = rpc.declare({ object: 'tailscale', method: 'get_firewall_status' });
|
||||||
let map;
|
let map;
|
||||||
let lastDevicesStatus;
|
let lastDevicesStatus;
|
||||||
|
|
||||||
|
// uci.changes() returns an object keyed by config name; count the entries so
|
||||||
|
// the firewall button can warn about unsaved work.
|
||||||
|
function countPending(changes) {
|
||||||
|
if (changes == null)
|
||||||
|
return 0;
|
||||||
|
if (Array.isArray(changes))
|
||||||
|
return changes.length;
|
||||||
|
let count = 0;
|
||||||
|
for (const key in changes)
|
||||||
|
count += Array.isArray(changes[key]) ? changes[key].length : 1;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ucode methods report business failures as { error: ... } in a *successful*
|
||||||
|
// ubus reply, so rpc.declare's rejection never fires for them. Every consumer of
|
||||||
|
// a write operation has to unwrap the reply itself; without this the UI happily
|
||||||
|
// shows "applied" for a reply that only says { error: '...' }.
|
||||||
|
function unwrapRpc(res, action) {
|
||||||
|
const what = action || _('Operation');
|
||||||
|
if (res == null)
|
||||||
|
throw new Error(_('Empty reply from server'));
|
||||||
|
if (res.error)
|
||||||
|
throw new Error(String(res.error));
|
||||||
|
if (res.success === false || res.ok === false) {
|
||||||
|
if (res.message)
|
||||||
|
throw new Error(String(res.message));
|
||||||
|
throw new Error(_('%s failed').format(what));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
const tailscaleSettingsConf = [
|
const tailscaleSettingsConf = [
|
||||||
[form.Flag, 'service_enabled', _('Enable Tailscale Service'), _('Enable or disable the Tailscale service. When disabled, the service will be stopped and the process will be killed.'), { rmempty: false }],
|
[form.Flag, 'service_enabled', _('Enable Tailscale Service'), _('Enable or disable the Tailscale service. When disabled, the service will be stopped and the process will be killed.'), { rmempty: false }],
|
||||||
[form.ListValue, 'fw_mode', _('Firewall Mode'), _('Select the firewall backend for Tailscale to use. Requires service restart to take effect.'), {values: ['nftables','iptables'],rmempty: false}],
|
[form.ListValue, 'fw_mode', _('Firewall Mode'), _('Select the firewall backend for Tailscale to use. Requires service restart to take effect.'), {values: ['nftables','iptables'],rmempty: false}],
|
||||||
@@ -78,6 +111,94 @@ function getRunningStatus() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Poll the background firewall job until it finishes. A poll that fails
|
||||||
|
// (network reloading, the connection dropped for a moment) is expected and is
|
||||||
|
// simply retried on the next tick instead of being reported as an error.
|
||||||
|
function pollFirewallJob(runId, timeoutSec) {
|
||||||
|
const deadline = Date.now() + (timeoutSec || 30) * 1000;
|
||||||
|
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
let lastState = '';
|
||||||
|
let finished = false;
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
if (finished)
|
||||||
|
return false;
|
||||||
|
finished = true;
|
||||||
|
clearTimeout(timer);
|
||||||
|
L.Poll.remove(poll);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function giveUp(message) {
|
||||||
|
if (stop())
|
||||||
|
reject(new Error(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
if (finished)
|
||||||
|
return Promise.resolve();
|
||||||
|
|
||||||
|
if (Date.now() > deadline) {
|
||||||
|
giveUp(_('Timed out waiting for the firewall reload'));
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return callGetFirewallStatus().then(function(res) {
|
||||||
|
if (finished)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// a result from an earlier run is not this run's answer
|
||||||
|
if (runId && res?.run_id && res.run_id !== runId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const state = res?.state || 'unknown';
|
||||||
|
|
||||||
|
if (state === 'done') {
|
||||||
|
if (stop())
|
||||||
|
ui.addTimeLimitedNotification(null, [ E('p', _('Firewall configuration applied.')) ], 5000, 'info');
|
||||||
|
resolve(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === 'failed') {
|
||||||
|
giveUp(res?.message || _('Firewall reload failed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === 'busy') {
|
||||||
|
giveUp(res?.message || _('Another firewall reload is already running'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state !== lastState) {
|
||||||
|
lastState = state;
|
||||||
|
if (state === 'running' && res?.step === 'firewall')
|
||||||
|
ui.addTimeLimitedNotification(null, [ E('p', _('Reloading firewall...')) ], 3000, 'info');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function poll() {
|
||||||
|
if (finished)
|
||||||
|
return Promise.resolve();
|
||||||
|
// a dropped connection during the reload is expected: leave it to
|
||||||
|
// the next tick, the timer below still guarantees termination
|
||||||
|
return check().catch(function() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// L.Poll skips a tick while the previous call is still pending, so a
|
||||||
|
// request that never settles would stop the polling loop entirely.
|
||||||
|
// The timer is what actually ends the wait.
|
||||||
|
const timer = setTimeout(function() {
|
||||||
|
giveUp(_('Timed out waiting for the firewall reload'));
|
||||||
|
}, (timeoutSec || 30) * 1000);
|
||||||
|
|
||||||
|
L.Poll.add(poll, 2);
|
||||||
|
poll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function formatBytes(bytes) {
|
function formatBytes(bytes) {
|
||||||
const bytes_num = parseInt(bytes, 10);
|
const bytes_num = parseInt(bytes, 10);
|
||||||
if (isNaN(bytes_num) || bytes_num === 0) return '-';
|
if (isNaN(bytes_num) || bytes_num === 0) return '-';
|
||||||
@@ -522,21 +643,46 @@ return view.extend({
|
|||||||
|
|
||||||
const fwBtn = s.taboption('general', form.Button, '_setup_firewall', _('Auto Configure Firewall'));
|
const fwBtn = s.taboption('general', form.Button, '_setup_firewall', _('Auto Configure Firewall'));
|
||||||
fwBtn.description = _('Essential configuration for Subnet Routing (Site-to-Site) and Exit Node features.')
|
fwBtn.description = _('Essential configuration for Subnet Routing (Site-to-Site) and Exit Node features.')
|
||||||
+'<br>'+_('It automatically creates the tailscale interface, sets up firewall zones for LAN <-> Tailscale forwarding,')
|
+'<br>'+_('It creates the tailscale interface, adds it to a firewall zone, and sets up forwarding between LAN and Tailscale, plus Tailscale to WAN.')
|
||||||
+'<br>'+_('and enables Masquerading and MSS Clamping (MTU fix) to ensure stable connections.');
|
+'<br>'+_('The Tailscale to WAN forwarding is what lets clients send their internet traffic through this device as an Exit Node.')
|
||||||
|
+'<br>'+_('Masquerading and MSS Clamping (MTU fix) are enabled to keep connections stable.');
|
||||||
fwBtn.inputstyle = 'action';
|
fwBtn.inputstyle = 'action';
|
||||||
fwBtn.onclick = function() {
|
fwBtn.onclick = function() {
|
||||||
return callSetupFirewall().then(function(res) {
|
// The button writes network/firewall UCI directly, so staged changes
|
||||||
const msg = res?.message || _('Firewall configuration applied.');
|
// in the form above would be left unapplied and the user would be
|
||||||
ui.addNotification(null, E('p', {}, msg), 'info');
|
// comparing the firewall against an old config. Ask at click time
|
||||||
}).catch(function(err) {
|
// rather than trusting a snapshot: uci.changes() is an RPC and the
|
||||||
ui.addNotification(null, E('p', {}, _('Failed to configure firewall: %s').format(err?.message || err || 'Unknown error')), 'error');
|
// answer goes stale as soon as the page is saved once.
|
||||||
}).then(function() {
|
// This only warns; blocking would misfire whenever the answer is
|
||||||
return new Promise(function(resolve) {
|
// stale, and the apply below is harmless either way.
|
||||||
window.setTimeout(resolve, 3000);
|
return L.resolveDefault(uci.changes(), {}).then(function(changes) {
|
||||||
|
if (countPending(changes) > 0) {
|
||||||
|
ui.addNotification(null, E('p', {}, _('You have unapplied changes. Save & Apply them first so the firewall matches the form.')), 'warning');
|
||||||
|
}
|
||||||
|
return runSetupFirewall();
|
||||||
|
}).catch(function() {
|
||||||
|
return runSetupFirewall();
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
// The reload runs in the background and can briefly interrupt the
|
||||||
|
// connection carrying this page, so the reply only confirms the commit;
|
||||||
|
// the outcome is polled separately.
|
||||||
|
function runSetupFirewall() {
|
||||||
|
return callSetupFirewall().then(function(res) {
|
||||||
|
unwrapRpc(res, _('Configuring firewall'));
|
||||||
|
|
||||||
|
if (!res.changed_network && !res.changed_firewall) {
|
||||||
|
ui.addNotification(null, E('p', {}, _('Firewall configuration already applied.')), 'info');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.addTimeLimitedNotification(null, [ E('p', _('Configuration committed, reloading network and firewall. This can take a moment.')) ], 5000, 'info');
|
||||||
|
return pollFirewallJob(res.run_id);
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.addNotification(null, E('p', {}, _('Failed to configure firewall: %s').format(err?.message || err?.error || err || _('Unknown error'))), 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const helpTitle = s.taboption('general', form.DummyValue, '_help_title');
|
const helpTitle = s.taboption('general', form.DummyValue, '_help_title');
|
||||||
helpTitle.title = _('How to enable Site-to-Site?');
|
helpTitle.title = _('How to enable Site-to-Site?');
|
||||||
@@ -552,6 +698,29 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Exit Node is a different feature with a different step that LuCI cannot
|
||||||
|
// complete on its own: the admin console approval happens in the user's
|
||||||
|
// Tailscale account. Without it a client reaches the LAN but gets no
|
||||||
|
// internet, which reads as "the firewall button did not work".
|
||||||
|
const exitHelp = s.taboption('general', form.DummyValue, '_exit_node_help');
|
||||||
|
exitHelp.title = _('How to enable Exit Node?');
|
||||||
|
exitHelp.render = function() {
|
||||||
|
return E('div', { 'class': 'cbi-value', 'style': 'margin-top: 1em; border-top: 1px font-weight: bold;' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, this.title),
|
||||||
|
E('div', { 'class': 'cbi-value-field', 'style': 'line-height: 1.6em; font-size: 95%; color: #555;' }, [
|
||||||
|
_('1. Enable "Advertise Exit Node".'), E('br'),
|
||||||
|
_('2. Click "Auto Configure Firewall" (it forwards Tailscale traffic to WAN).'), E('br'),
|
||||||
|
_('3. In the Tailscale admin console, edit this device under the route settings and approve it as an Exit Node.'), E('br'),
|
||||||
|
// No quotes around the client's control on purpose: "Exit node"
|
||||||
|
// is a menu in the Tailscale client, not a field on this page,
|
||||||
|
// and quoting it would send the reader to the dropdown above,
|
||||||
|
// which selects the opposite direction.
|
||||||
|
_('4. On the client, set this device as its exit node.'), E('br'),
|
||||||
|
E('strong', { 'style': 'color: #d9534f;' }, _('[Important] Step 3 cannot be done here: it needs your Tailscale account.'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
// Create the account settings
|
// Create the account settings
|
||||||
s.tab('account', _('Account Settings'));
|
s.tab('account', _('Account Settings'));
|
||||||
defTabOpts(s, 'account', accountConf, { optional: false });
|
defTabOpts(s, 'account', accountConf, { optional: false });
|
||||||
@@ -617,7 +786,7 @@ return view.extend({
|
|||||||
doc.body.innerHTML =
|
doc.body.innerHTML =
|
||||||
'<h2>' + _('Error') + '</h2>' +
|
'<h2>' + _('Error') + '</h2>' +
|
||||||
'<p>' + _('Failed to get login URL. You may close this tab.') + '</p>';
|
'<p>' + _('Failed to get login URL. You may close this tab.') + '</p>';
|
||||||
ui.addTimeLimitedNotification(null, [ E('p', _('Failed to get login URL: Invalid response from server.')) ], 7000, 'error');
|
ui.addTimeLimitedNotification(null, [ E('p', _('Failed to get login URL: %s').format(res?.error || _('Invalid response from server.'))) ], 7000, 'error');
|
||||||
}
|
}
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
@@ -643,6 +812,7 @@ return view.extend({
|
|||||||
ui.showModal(_('Logging out...'), E('em', {}, _('Please wait.')));
|
ui.showModal(_('Logging out...'), E('em', {}, _('Please wait.')));
|
||||||
|
|
||||||
return callDoLogout().then(function(res) {
|
return callDoLogout().then(function(res) {
|
||||||
|
unwrapRpc(res, _('Logout'));
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
ui.addTimeLimitedNotification(null, [ E('p', _('Successfully logged out.')) ], 5000, 'info');
|
ui.addTimeLimitedNotification(null, [ E('p', _('Successfully logged out.')) ], 5000, 'info');
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
@@ -680,6 +850,7 @@ return view.extend({
|
|||||||
display.replaceChildren(E('em', {}, _('Collecting data ...')));
|
display.replaceChildren(E('em', {}, _('Collecting data ...')));
|
||||||
}
|
}
|
||||||
return callGetLogs().then(function(res) {
|
return callGetLogs().then(function(res) {
|
||||||
|
unwrapRpc(res, _('Loading logs'));
|
||||||
if (display) {
|
if (display) {
|
||||||
display.replaceChildren(renderLogs(res));
|
display.replaceChildren(renderLogs(res));
|
||||||
}
|
}
|
||||||
@@ -701,7 +872,26 @@ return view.extend({
|
|||||||
// standard OpenWrt apply mechanism, which triggers /etc/init.d/tailscale-settings
|
// standard OpenWrt apply mechanism, which triggers /etc/init.d/tailscale-settings
|
||||||
// and provides automatic rollback protection if the device becomes unreachable.
|
// and provides automatic rollback protection if the device becomes unreachable.
|
||||||
handleSaveApply(ev, mode) {
|
handleSaveApply(ev, mode) {
|
||||||
|
// Read the submitted value before map.save() consumes the staged delta.
|
||||||
|
// uci.get() prefers the staged change over the saved value, and the
|
||||||
|
// staged delta is only populated once the form has been parsed, which
|
||||||
|
// map.save() guarantees here.
|
||||||
|
const wantEnabled = uci.get('tailscale', 'settings', 'service_enabled');
|
||||||
|
|
||||||
return map.save().then(function () {
|
return map.save().then(function () {
|
||||||
|
// Only records the disable request; the reload handler performs
|
||||||
|
// the stop after the commit, so a LuCI rollback still finds a
|
||||||
|
// running daemon. One concurrency slot on purpose: an overlapping
|
||||||
|
// apply could let the trigger read a half-written request file.
|
||||||
|
// Fail-open on purpose: an install whose rpcd ACL cache predates
|
||||||
|
// this method would otherwise be unable to apply anything. The
|
||||||
|
// worst case is that the stop is skipped for this apply.
|
||||||
|
return callSetServiceEnabled({ enabled: wantEnabled === '0' ? '0' : '1' })
|
||||||
|
.catch(function(err) {
|
||||||
|
console.warn('set_service_enabled failed: %s', err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
return ui.changes.apply(mode == '0');
|
return ui.changes.apply(mode == '0');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,563 +1,676 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:47
|
||||||
|
msgid "%s failed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:71
|
||||||
msgid "(Experimental) Reduce Memory Usage"
|
msgid "(Experimental) Reduce Memory Usage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:711
|
||||||
|
msgid "1. Enable \"Advertise Exit Node\"."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:693
|
||||||
msgid "1. Select \"Accept Routes\" (to access remote devices)."
|
msgid "1. Select \"Accept Routes\" (to access remote devices)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:712
|
||||||
|
msgid ""
|
||||||
|
"2. Click \"Auto Configure Firewall\" (it forwards Tailscale traffic "
|
||||||
|
"to WAN)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:694
|
||||||
msgid ""
|
msgid ""
|
||||||
"2. In \"Advertise Routes\", select your local subnet (to allow remote "
|
"2. In \"Advertise Routes\", select your local subnet (to allow remote "
|
||||||
"devices to access this LAN)."
|
"devices to access this LAN)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:471
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:695
|
||||||
msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
|
msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:713
|
||||||
|
msgid ""
|
||||||
|
"3. In the Tailscale admin console, edit this device under the route "
|
||||||
|
"settings and approve it as an Exit Node."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:718
|
||||||
|
msgid "4. On the client, set this device as its exit node."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:55
|
||||||
msgid "Accept Routes"
|
msgid "Accept Routes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:725
|
||||||
msgid "Account Settings"
|
msgid "Account Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:56
|
||||||
msgid "Advertise Exit Node"
|
msgid "Advertise Exit Node"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:636
|
||||||
msgid "Advertise Routes"
|
msgid "Advertise Routes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:636
|
||||||
msgid ""
|
msgid ""
|
||||||
"Advertise subnet routes behind this device. Select from the detected subnets "
|
"Advertise subnet routes behind this device. Select from the detected "
|
||||||
"below or enter custom routes (comma-separated)."
|
"subnets below or enter custom routes (comma-separated)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:57
|
||||||
msgid "Allow LAN Access"
|
msgid "Allow LAN Access"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:55
|
||||||
msgid "Allow accepting routes announced by other nodes."
|
msgid "Allow accepting routes announced by other nodes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:61
|
||||||
msgid "Allow connecting to this device through the SSH function of Tailscale."
|
msgid "Allow connecting to this device through the SSH function of Tailscale."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:3
|
#: luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:1
|
||||||
msgid "Allow user access to tailscale"
|
msgid "Allow user access to tailscale"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:552
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:170
|
||||||
|
msgid "Another firewall reload is already running"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:799
|
||||||
msgid "Are you sure you want to log out?"
|
msgid "Are you sure you want to log out?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:644
|
||||||
msgid "Auto Configure Firewall"
|
msgid "Auto Configure Firewall"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:559
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:806
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:750
|
||||||
msgid "Click to Log out account on this device."
|
msgid "Click to Log out account on this device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:482
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:729
|
||||||
msgid "Click to get a login URL for this device."
|
msgid "Click to get a login URL for this device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:169
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:291
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:383
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:392
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:601
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:850
|
||||||
msgid "Collecting data ..."
|
msgid "Collecting data ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:680
|
||||||
|
msgid ""
|
||||||
|
"Configuration committed, reloading network and firewall. This can "
|
||||||
|
"take a moment."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:673
|
||||||
|
msgid "Configuring firewall"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:826
|
||||||
msgid "Confirm Logout"
|
msgid "Confirm Logout"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:281
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
|
||||||
msgid "Connection Info"
|
msgid "Connection Info"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "Controls how Tailscale DNS is handled."
|
msgid "Controls how Tailscale DNS is handled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:762
|
||||||
msgid ""
|
msgid ""
|
||||||
"Could not open a new tab. Please check if your browser or an extension "
|
"Could not open a new tab. Please check if your browser or an "
|
||||||
"blocked the pop-up."
|
"extension blocked the pop-up."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:487
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:734
|
||||||
msgid "Custom Login Server"
|
msgid "Custom Login Server"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:742
|
||||||
msgid "Custom Login Server Auth Key"
|
msgid "Custom Login Server Auth Key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "DNS Mode"
|
msgid "DNS Mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:70
|
||||||
|
msgid "Daemon MTU"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:865
|
||||||
|
msgid "Daemon Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:56
|
||||||
msgid "Declare this device as an Exit Node."
|
msgid "Declare this device as an Exit Node."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:581
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:829
|
||||||
msgid "Devices List"
|
msgid "Devices List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:63
|
||||||
msgid "Disable Firewall Configuration"
|
msgid "Disable Firewall Configuration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:59
|
||||||
msgid "Disable SNAT"
|
msgid "Disable SNAT"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:59
|
||||||
msgid ""
|
msgid ""
|
||||||
"Disable Source NAT (SNAT) for traffic to advertised routes. Most users "
|
"Disable Source NAT (SNAT) for traffic to advertised routes. Most "
|
||||||
"should leave this unchecked."
|
"users should leave this unchecked."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:63
|
||||||
msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)."
|
msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "Disabled"
|
msgid "Disabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "Disabled: system DNS only."
|
msgid "Disabled: system DNS only."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:751
|
||||||
msgid "Disconnect from Tailscale and expire current node key."
|
msgid "Disconnect from Tailscale and expire current node key."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:41
|
||||||
|
msgid "Empty reply from server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:64
|
||||||
msgid "Enable Peer Relay"
|
msgid "Enable Peer Relay"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:61
|
||||||
msgid "Enable Tailscale SSH"
|
msgid "Enable Tailscale SSH"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:53
|
||||||
msgid "Enable Tailscale Service"
|
msgid "Enable Tailscale Service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:58
|
||||||
msgid "Enable Web Interface"
|
msgid "Enable Web Interface"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:53
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enable or disable the Tailscale service. When disabled, the service will be "
|
"Enable or disable the Tailscale service. When disabled, the service "
|
||||||
"stopped and the process will be killed."
|
"will be stopped and the process will be killed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:64
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enable this device as a Peer Relay server. Requires a public IP and an UDP "
|
"Enable this device as a Peer Relay server. Requires a public IP and "
|
||||||
"port open on the router."
|
"an UDP port open on the router."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:71
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enabling this option can reduce memory usage, but it may sacrifice some "
|
"Enabling this option can reduce memory usage, but it may sacrifice "
|
||||||
"performance (set GOGC=10)."
|
"some performance (set GOGC=10)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:540
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:787
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:272
|
||||||
msgid "Error caching DERP region map: %s"
|
msgid "Error caching DERP region map: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:275
|
||||||
msgid "Error fetching DERP region map: %s"
|
msgid "Error fetching DERP region map: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
|
||||||
msgid "Error reading cached DERP region map: %s"
|
msgid "Error reading cached DERP region map: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:645
|
||||||
msgid ""
|
msgid ""
|
||||||
"Essential configuration for Subnet Routing (Site-to-Site) and Exit Node "
|
"Essential configuration for Subnet Routing (Site-to-Site) and Exit "
|
||||||
"features."
|
"Node features."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:612
|
||||||
msgid "Exit Node"
|
msgid "Exit Node"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:58
|
||||||
msgid ""
|
msgid ""
|
||||||
"Expose a web interface on port 5252 for managing this node over Tailscale."
|
"Expose a web interface on port 5252 for managing this node over "
|
||||||
|
"Tailscale."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:683
|
||||||
msgid "Failed to configure firewall: %s"
|
msgid "Failed to configure firewall: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:788
|
||||||
msgid "Failed to get login URL. You may close this tab."
|
msgid "Failed to get login URL. You may close this tab."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:789
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:793
|
||||||
msgid "Failed to get login URL: %s"
|
msgid "Failed to get login URL: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:859
|
||||||
msgid "Failed to get login URL: Invalid response from server."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609
|
|
||||||
msgid "Failed to load logs: %s"
|
msgid "Failed to load logs: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:338
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:487
|
||||||
msgid "Filter peers by name, IP, or OS..."
|
msgid "Filter peers by name, IP, or OS..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
|
||||||
msgid "Filter:"
|
msgid "Filter:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:54
|
||||||
msgid "Firewall Mode"
|
msgid "Firewall Mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:676
|
||||||
|
msgid "Firewall configuration already applied."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:159
|
||||||
msgid "Firewall configuration applied."
|
msgid "Firewall configuration applied."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:401
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:165
|
||||||
|
msgid "Firewall reload failed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:600
|
||||||
msgid "General Settings"
|
msgid "General Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:449
|
||||||
msgid "Hostname"
|
msgid "Hostname"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:464
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:706
|
||||||
|
msgid "How to enable Exit Node?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:688
|
||||||
msgid "How to enable Site-to-Site?"
|
msgid "How to enable Site-to-Site?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:483
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:730
|
||||||
msgid ""
|
msgid ""
|
||||||
"If the timeout is displayed, you can refresh the page and click Login again."
|
"If the timeout is displayed, you can refresh the page and click Login "
|
||||||
|
"again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:497
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:744
|
||||||
msgid ""
|
msgid ""
|
||||||
"If you are using custom login server but not providing an Auth Key, will "
|
"If you are using custom login server but not providing an Auth Key, "
|
||||||
"redirect to the login page without pre-filling the key."
|
"will redirect to the login page without pre-filling the key."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:93
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:215
|
||||||
msgid "Invalid Date"
|
msgid "Invalid Date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:447
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:789
|
||||||
msgid ""
|
msgid "Invalid response from server."
|
||||||
"It automatically creates the tailscale interface, sets up firewall zones for "
|
|
||||||
"LAN <-> Tailscale forwarding,"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:96
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:646
|
||||||
|
msgid ""
|
||||||
|
"It creates the tailscale interface, adds it to a firewall zone, and "
|
||||||
|
"sets up forwarding between LAN and Tailscale, plus Tailscale to WAN."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218
|
||||||
msgid "Just now"
|
msgid "Just now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:208
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:330
|
||||||
msgid "LOGGED OUT"
|
msgid "LOGGED OUT"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:284
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
|
||||||
msgid "Last Seen"
|
msgid "Last Seen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:489
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:736
|
||||||
msgid "Leave blank for default Tailscale control plane."
|
msgid "Leave blank for default Tailscale control plane."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:853
|
||||||
|
msgid "Loading logs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:812
|
||||||
msgid "Logging out..."
|
msgid "Logging out..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:481
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:728
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:502
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:749
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:575
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:815
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:823
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:820
|
||||||
msgid "Logout failed: %s"
|
msgid "Logout failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:587
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:836
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "MagicDNS: Tailscale overrides resolv.conf."
|
msgid "MagicDNS: Tailscale overrides resolv.conf."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:648
|
||||||
|
msgid ""
|
||||||
|
"Masquerading and MSS Clamping (MTU fix) are enabled to keep "
|
||||||
|
"connections stable."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:212
|
||||||
msgid "N/A"
|
msgid "N/A"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:219
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:341
|
||||||
msgid "NOT RUNNING"
|
msgid "NOT RUNNING"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:273
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:439
|
||||||
|
msgid "No matching peer devices found."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
|
||||||
msgid "No peer devices found."
|
msgid "No peer devices found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:246
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:368
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:840
|
||||||
msgid "No tailscale-related logs found."
|
msgid "No tailscale-related logs found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:414
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:613
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:91
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:213
|
||||||
msgid "Now"
|
msgid "Now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:280
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
|
||||||
msgid "OS"
|
msgid "OS"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
msgid "Offline"
|
msgid "Offline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
msgid "Online"
|
msgid "Online"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "OpenWrt Forward"
|
msgid "OpenWrt Forward"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)"
|
msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:39
|
||||||
msgid ""
|
msgid "Operation"
|
||||||
"Optional: Specify a custom control server URL (e.g., a Headscale instance, "
|
|
||||||
"%s)."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:735
|
||||||
msgid ""
|
msgid ""
|
||||||
"Optional: Specify an authentication key for the custom control server. Leave "
|
"Optional: Specify a custom control server URL (e.g., a Headscale "
|
||||||
"blank if not required."
|
"instance, %s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:405
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:743
|
||||||
|
msgid ""
|
||||||
|
"Optional: Specify an authentication key for the custom control "
|
||||||
|
"server. Leave blank if not required."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:604
|
||||||
msgid "Peer Relay Port"
|
msgid "Peer Relay Port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:210
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:332
|
||||||
msgid "Please use the login button in the settings below to authenticate."
|
msgid "Please use the login button in the settings below to authenticate."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:772
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:778
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:812
|
||||||
msgid "Please wait."
|
msgid "Please wait."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:349
|
||||||
msgid "RUNNING"
|
msgid "RUNNING"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:282
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:453
|
||||||
msgid "RX"
|
msgid "RX"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:596
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:845
|
||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:177
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531
|
msgid "Reloading firewall..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:772
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:778
|
||||||
msgid "Requesting Login URL..."
|
msgid "Requesting Login URL..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:769
|
||||||
msgid "Requesting Tailscale login URL... Please wait."
|
msgid "Requesting Tailscale login URL... Please wait."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:612
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select an exit node from the list. If enabled, Allow LAN Access is enabled "
|
"Select an exit node from the list. If enabled, Allow LAN Access is "
|
||||||
"implicitly."
|
"enabled implicitly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:54
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select the firewall backend for Tailscale to use. Requires service restart "
|
"Select the firewall backend for Tailscale to use. Requires service "
|
||||||
"to take effect."
|
"restart to take effect."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:198
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:320
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:206
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:328
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:340
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:349
|
||||||
msgid "Service Status"
|
msgid "Service Status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:70
|
||||||
|
msgid ""
|
||||||
|
"Set a custom MTU for the Tailscale daemon. Leave blank to use the "
|
||||||
|
"default value."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:60
|
||||||
msgid "Shields Up"
|
msgid "Shields Up"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:277
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:817
|
||||||
msgid "Successfully logged out."
|
msgid "Successfully logged out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:199
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:321
|
||||||
msgid "TAILSCALE NOT FOUND"
|
msgid "TAILSCALE NOT FOUND"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "TUN Mode"
|
msgid "TUN Mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:283
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:454
|
||||||
msgid "TX"
|
msgid "TX"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:232
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:354
|
||||||
msgid "Tailnet Name"
|
msgid "Tailnet Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:562
|
||||||
#: applications/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:3
|
#: luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:1
|
||||||
msgid "Tailscale"
|
msgid "Tailscale"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:175
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:297
|
||||||
msgid "Tailscale Health Check: %s"
|
msgid "Tailscale Health Check: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:279
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:450
|
||||||
msgid "Tailscale IP"
|
msgid "Tailscale IP"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:230
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:352
|
||||||
msgid "Tailscale IPv4"
|
msgid "Tailscale IPv4"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:231
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:353
|
||||||
msgid "Tailscale IPv6"
|
msgid "Tailscale IPv6"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:521
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:768
|
||||||
msgid "Tailscale Login"
|
msgid "Tailscale Login"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:562
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tailscale is a mesh VPN solution that makes it easy to connect your devices "
|
"Tailscale is a mesh VPN solution that makes it easy to connect your "
|
||||||
"securely. This configuration page allows you to manage Tailscale settings on "
|
"devices securely. This configuration page allows you to manage "
|
||||||
"your OpenWrt device."
|
"Tailscale settings on your OpenWrt device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:264
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:389
|
||||||
msgid "Tailscale status error"
|
msgid "Tailscale status error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:523
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:647
|
||||||
|
msgid ""
|
||||||
|
"The Tailscale to WAN forwarding is what lets clients send their "
|
||||||
|
"internet traffic through this device as an Exit Node."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:770
|
||||||
msgid "This can take up to 30 seconds."
|
msgid "This can take up to 30 seconds."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:553
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:800
|
||||||
msgid ""
|
msgid ""
|
||||||
"This will disconnect this device from your Tailnet and require you to re-"
|
"This will disconnect this device from your Tailnet and require you to "
|
||||||
"authenticate."
|
"re-authenticate."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:406
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:143
|
||||||
msgid ""
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:194
|
||||||
"UDP port for the Peer Relay service. Open this port on your router firewall/"
|
msgid "Timed out waiting for the firewall reload"
|
||||||
"NAT."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:605
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
|
msgid ""
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153
|
"UDP port for the Peer Relay service. Open this port on your router "
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546
|
"firewall/NAT."
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572
|
msgstr ""
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:272
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:275
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:683
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:793
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:820
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:859
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:350
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:60
|
||||||
msgid ""
|
msgid ""
|
||||||
"When enabled, blocks all inbound connections from the Tailscale network."
|
"When enabled, blocks all inbound connections from the Tailscale "
|
||||||
|
"network."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:57
|
||||||
msgid "When using the exit node, access to the local LAN is allowed."
|
msgid "When using the exit node, access to the local LAN is allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:472
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:660
|
||||||
|
msgid ""
|
||||||
|
"You have unapplied changes. Save & Apply them first so the firewall "
|
||||||
|
"matches the form."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:696
|
||||||
msgid ""
|
msgid ""
|
||||||
"[Important] Log in to the Tailscale admin console and manually enable "
|
"[Important] Log in to the Tailscale admin console and manually enable "
|
||||||
"\"Subnet Routes\" for this device."
|
"\"Subnet Routes\" for this device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:99
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:719
|
||||||
msgid "ago"
|
msgid ""
|
||||||
|
"[Important] Step 3 cannot be done here: it needs your Tailscale "
|
||||||
|
"account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:221
|
||||||
msgid ""
|
msgid "ago"
|
||||||
"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable "
|
|
||||||
"connections."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1,585 +1,693 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:47
|
||||||
|
msgid "%s failed"
|
||||||
|
msgstr "%s失败"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:71
|
||||||
msgid "(Experimental) Reduce Memory Usage"
|
msgid "(Experimental) Reduce Memory Usage"
|
||||||
msgstr "(实验性) 减少内存使用"
|
msgstr "(实验性) 减少内存使用"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:711
|
||||||
msgid "1. Select \"Accept Routes\" (to access remote devices)."
|
msgid "1. Enable \"Advertise Exit Node\"."
|
||||||
msgstr "1. 勾选 接受路由 (接受远程设备访问)。"
|
msgstr "1. 勾选 \"通告出口节点\"。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:693
|
||||||
|
msgid "1. Select \"Accept Routes\" (to access remote devices)."
|
||||||
|
msgstr "1. 勾选 \"接受路由\" (以访问远程设备)。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:712
|
||||||
|
msgid ""
|
||||||
|
"2. Click \"Auto Configure Firewall\" (it forwards Tailscale traffic "
|
||||||
|
"to WAN)."
|
||||||
|
msgstr "2. 点击 \"自动配置防火墙\" (它会将 Tailscale 流量转发到 WAN)。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:694
|
||||||
msgid ""
|
msgid ""
|
||||||
"2. In \"Advertise Routes\", select your local subnet (to allow remote "
|
"2. In \"Advertise Routes\", select your local subnet (to allow remote "
|
||||||
"devices to access this LAN)."
|
"devices to access this LAN)."
|
||||||
msgstr "2. 在 通告路由 选择本设备的局域网段 (让远程设备能访问你)。"
|
msgstr "2. 在 \"通告路由\" 中选择本地子网 (让远程设备能访问本局域网)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:471
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:695
|
||||||
msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
|
msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
|
||||||
msgstr "3. 点击 自动配置防火墙 (打通防火墙转发)。"
|
msgstr "3. 点击 \"自动配置防火墙\" (打通防火墙转发)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:713
|
||||||
|
msgid ""
|
||||||
|
"3. In the Tailscale admin console, edit this device under the route "
|
||||||
|
"settings and approve it as an Exit Node."
|
||||||
|
msgstr "3. 在 Tailscale 管理控制台的路由设置中编辑此设备,并批准其为出口节点。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:718
|
||||||
|
msgid "4. On the client, set this device as its exit node."
|
||||||
|
msgstr "4. 在客户端上,将此设备设为出口节点。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:55
|
||||||
msgid "Accept Routes"
|
msgid "Accept Routes"
|
||||||
msgstr "接受路由"
|
msgstr "接受路由"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:725
|
||||||
msgid "Account Settings"
|
msgid "Account Settings"
|
||||||
msgstr "账户设置"
|
msgstr "账户设置"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:56
|
||||||
msgid "Advertise Exit Node"
|
msgid "Advertise Exit Node"
|
||||||
msgstr "通告出口节点"
|
msgstr "通告出口节点"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:636
|
||||||
msgid "Advertise Routes"
|
msgid "Advertise Routes"
|
||||||
msgstr "通告路由"
|
msgstr "通告路由"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:636
|
||||||
msgid ""
|
msgid ""
|
||||||
"Advertise subnet routes behind this device. Select from the detected subnets "
|
"Advertise subnet routes behind this device. Select from the detected "
|
||||||
"below or enter custom routes (comma-separated)."
|
"subnets below or enter custom routes (comma-separated)."
|
||||||
msgstr ""
|
msgstr "通告此设备后的子网路由。从下面的子网中选择,或输入自定义路由 (逗号分隔)。"
|
||||||
"通告此设备后的子网路由。从下面的子网中选择,或输入自定义路由 (逗号分隔)。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:57
|
||||||
msgid "Allow LAN Access"
|
msgid "Allow LAN Access"
|
||||||
msgstr "允许局域网访问"
|
msgstr "允许访问局域网"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:55
|
||||||
msgid "Allow accepting routes announced by other nodes."
|
msgid "Allow accepting routes announced by other nodes."
|
||||||
msgstr "允许接受由其他节点通告的路由。"
|
msgstr "允许接受由其他节点通告的路由。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:61
|
||||||
msgid "Allow connecting to this device through the SSH function of Tailscale."
|
msgid "Allow connecting to this device through the SSH function of Tailscale."
|
||||||
msgstr "允许通过 Tailscale 的 SSH 功能连接到此设备。"
|
msgstr "允许通过 Tailscale 的 SSH 功能连接到此设备。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:3
|
#: luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:1
|
||||||
msgid "Allow user access to tailscale"
|
msgid "Allow user access to tailscale"
|
||||||
msgstr "允许用户访问 Tailscale"
|
msgstr "允许用户访问 Tailscale"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:552
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:170
|
||||||
|
msgid "Another firewall reload is already running"
|
||||||
|
msgstr "另一个防火墙重新加载任务正在进行中"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:799
|
||||||
msgid "Are you sure you want to log out?"
|
msgid "Are you sure you want to log out?"
|
||||||
msgstr "您确定要登出吗?"
|
msgstr "您确定要登出吗?"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:644
|
||||||
msgid "Auto Configure Firewall"
|
msgid "Auto Configure Firewall"
|
||||||
msgstr "自动配置防火墙"
|
msgstr "自动配置防火墙"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:559
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:806
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "取消"
|
msgstr "取消"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:750
|
||||||
msgid "Click to Log out account on this device."
|
msgid "Click to Log out account on this device."
|
||||||
msgstr "点击以登出此设备上的账户。"
|
msgstr "点击以登出此设备上的账户。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:482
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:729
|
||||||
msgid "Click to get a login URL for this device."
|
msgid "Click to get a login URL for this device."
|
||||||
msgstr "点击获取此设备的登录 URL。"
|
msgstr "点击获取此设备的登录 URL。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:169
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:291
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:383
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:392
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:601
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:850
|
||||||
msgid "Collecting data ..."
|
msgid "Collecting data ..."
|
||||||
msgstr "正在收集数据..."
|
msgstr "正在收集数据..."
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:680
|
||||||
|
msgid ""
|
||||||
|
"Configuration committed, reloading network and firewall. This can "
|
||||||
|
"take a moment."
|
||||||
|
msgstr "配置已提交,正在重新加载网络和防火墙。这可能需要一点时间。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:673
|
||||||
|
msgid "Configuring firewall"
|
||||||
|
msgstr "正在配置防火墙"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:826
|
||||||
msgid "Confirm Logout"
|
msgid "Confirm Logout"
|
||||||
msgstr "确认登出"
|
msgstr "确认登出"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:281
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
|
||||||
msgid "Connection Info"
|
msgid "Connection Info"
|
||||||
msgstr "连接信息"
|
msgstr "连接信息"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "Controls how Tailscale DNS is handled."
|
msgid "Controls how Tailscale DNS is handled."
|
||||||
msgstr "控制 Tailscale DNS 的处理方式。"
|
msgstr "控制 Tailscale DNS 的处理方式。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:762
|
||||||
msgid ""
|
msgid ""
|
||||||
"Could not open a new tab. Please check if your browser or an extension "
|
"Could not open a new tab. Please check if your browser or an "
|
||||||
"blocked the pop-up."
|
"extension blocked the pop-up."
|
||||||
msgstr "无法打开新标签页。请检查您的浏览器或扩展程序是否阻止了弹出窗口。"
|
msgstr "无法打开新标签页。请检查您的浏览器或扩展程序是否阻止了弹出窗口。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:487
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:734
|
||||||
msgid "Custom Login Server"
|
msgid "Custom Login Server"
|
||||||
msgstr "自定义登录服务器"
|
msgstr "自定义登录服务器"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:742
|
||||||
msgid "Custom Login Server Auth Key"
|
msgid "Custom Login Server Auth Key"
|
||||||
msgstr "自定义登录服务器认证密钥"
|
msgstr "自定义登录服务器认证密钥"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "DNS Mode"
|
msgid "DNS Mode"
|
||||||
msgstr "DNS 模式"
|
msgstr "DNS 模式"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:70
|
||||||
|
msgid "Daemon MTU"
|
||||||
|
msgstr "守护进程 MTU"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:865
|
||||||
|
msgid "Daemon Settings"
|
||||||
|
msgstr "守护进程设置"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:56
|
||||||
msgid "Declare this device as an Exit Node."
|
msgid "Declare this device as an Exit Node."
|
||||||
msgstr "将此设备声明为出口节点。"
|
msgstr "将此设备声明为出口节点。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:581
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:829
|
||||||
msgid "Devices List"
|
msgid "Devices List"
|
||||||
msgstr "设备列表"
|
msgstr "设备列表"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:63
|
||||||
msgid "Disable Firewall Configuration"
|
msgid "Disable Firewall Configuration"
|
||||||
msgstr "禁用防火墙配置"
|
msgstr "禁用防火墙配置"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:59
|
||||||
msgid "Disable SNAT"
|
msgid "Disable SNAT"
|
||||||
msgstr "禁用 SNAT"
|
msgstr "禁用 SNAT"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:59
|
||||||
msgid ""
|
msgid ""
|
||||||
"Disable Source NAT (SNAT) for traffic to advertised routes. Most users "
|
"Disable Source NAT (SNAT) for traffic to advertised routes. Most "
|
||||||
"should leave this unchecked."
|
"users should leave this unchecked."
|
||||||
msgstr "为通告路由的流量禁用源地址转换 (SNAT)。大多数用户应保持此项不勾选。"
|
msgstr "为通告路由的流量禁用源地址转换 (SNAT)。大多数用户应保持此项不勾选。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:63
|
||||||
msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)."
|
msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)."
|
||||||
msgstr ""
|
msgstr "禁用 Tailscale netfilter 自动配置 (--netfilter-mode=off)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "Disabled"
|
msgid "Disabled"
|
||||||
msgstr "已禁用"
|
msgstr "已禁用"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "Disabled: system DNS only."
|
msgid "Disabled: system DNS only."
|
||||||
msgstr "已禁用:使用系统指定DNS模式"
|
msgstr "已禁用:仅使用系统 DNS。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:751
|
||||||
msgid "Disconnect from Tailscale and expire current node key."
|
msgid "Disconnect from Tailscale and expire current node key."
|
||||||
msgstr "从 Tailscale 断开连接并使当前节点密钥过期。"
|
msgstr "从 Tailscale 断开连接并使当前节点密钥过期。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:41
|
||||||
|
msgid "Empty reply from server"
|
||||||
|
msgstr "服务器返回空响应"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:64
|
||||||
msgid "Enable Peer Relay"
|
msgid "Enable Peer Relay"
|
||||||
msgstr "启用节点中继"
|
msgstr "启用节点中继"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:61
|
||||||
msgid "Enable Tailscale SSH"
|
msgid "Enable Tailscale SSH"
|
||||||
msgstr "启用 Tailscale SSH"
|
msgstr "启用 Tailscale SSH"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:53
|
||||||
msgid "Enable Tailscale Service"
|
msgid "Enable Tailscale Service"
|
||||||
msgstr "Tailscale服务开关"
|
msgstr "启用 Tailscale 服务"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:58
|
||||||
msgid "Enable Web Interface"
|
msgid "Enable Web Interface"
|
||||||
msgstr "启用 Web 界面"
|
msgstr "启用 Web 界面"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:53
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enable or disable the Tailscale service. When disabled, the service will be "
|
"Enable or disable the Tailscale service. When disabled, the service "
|
||||||
"stopped and the process will be killed."
|
"will be stopped and the process will be killed."
|
||||||
msgstr "启用或禁用Tailscale服务。禁用后,服务将被停止,当前进程将被终止。"
|
msgstr "启用或禁用Tailscale服务。禁用后,服务将被停止,当前进程将被终止。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:64
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enable this device as a Peer Relay server. Requires a public IP and an UDP "
|
"Enable this device as a Peer Relay server. Requires a public IP and "
|
||||||
"port open on the router."
|
"an UDP port open on the router."
|
||||||
msgstr ""
|
msgstr "将此设备设为节点中继服务器。需要此路由器具备公网 IP,并在路由器上开放一个 UDP 端口。"
|
||||||
"将此设备设定为节点中继服务器(DERP)。需要此路由器具备公网IP以及开放一个UDP端"
|
|
||||||
"口"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr "已启用"
|
msgstr "已启用"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:71
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enabling this option can reduce memory usage, but it may sacrifice some "
|
"Enabling this option can reduce memory usage, but it may sacrifice "
|
||||||
"performance (set GOGC=10)."
|
"some performance (set GOGC=10)."
|
||||||
msgstr "启用此选项可以减少内存使用,但可能会牺牲一些性能 (设置 GOGC=10)。"
|
msgstr "启用此选项可以减少内存使用,但可能会牺牲一些性能 (设置 GOGC=10)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:540
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:787
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "错误"
|
msgstr "错误"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:272
|
||||||
msgid "Error caching DERP region map: %s"
|
msgid "Error caching DERP region map: %s"
|
||||||
msgstr "缓存 DERP 区域地图时出错: %s"
|
msgstr "缓存 DERP 区域地图时出错: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:275
|
||||||
msgid "Error fetching DERP region map: %s"
|
msgid "Error fetching DERP region map: %s"
|
||||||
msgstr "获取 DERP 区域地图时出错: %s"
|
msgstr "获取 DERP 区域地图时出错: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
|
||||||
msgid "Error reading cached DERP region map: %s"
|
msgid "Error reading cached DERP region map: %s"
|
||||||
msgstr "读取缓存的 DERP 区域地图时出错: %s"
|
msgstr "读取缓存的 DERP 区域地图时出错: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:645
|
||||||
msgid ""
|
msgid ""
|
||||||
"Essential configuration for Subnet Routing (Site-to-Site) and Exit Node "
|
"Essential configuration for Subnet Routing (Site-to-Site) and Exit "
|
||||||
"features."
|
"Node features."
|
||||||
msgstr "子网路由(局域网间互通)和出口节点的基本配置。"
|
msgstr "子网路由(局域网间互通)和出口节点的基本配置。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:612
|
||||||
msgid "Exit Node"
|
msgid "Exit Node"
|
||||||
msgstr "出口节点"
|
msgstr "出口节点"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:58
|
||||||
msgid ""
|
msgid ""
|
||||||
"Expose a web interface on port 5252 for managing this node over Tailscale."
|
"Expose a web interface on port 5252 for managing this node over "
|
||||||
|
"Tailscale."
|
||||||
msgstr "在端口 5252 上暴露一个 Web 界面,用于通过 Tailscale 管理此节点。"
|
msgstr "在端口 5252 上暴露一个 Web 界面,用于通过 Tailscale 管理此节点。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:683
|
||||||
msgid "Failed to configure firewall: %s"
|
msgid "Failed to configure firewall: %s"
|
||||||
msgstr "获取防火墙设置失败: %s"
|
msgstr "配置防火墙失败: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:788
|
||||||
msgid "Failed to get login URL. You may close this tab."
|
msgid "Failed to get login URL. You may close this tab."
|
||||||
msgstr "获取登录 URL 失败。您可以关闭此标签页。"
|
msgstr "获取登录 URL 失败。您可以关闭此标签页。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:789
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:793
|
||||||
msgid "Failed to get login URL: %s"
|
msgid "Failed to get login URL: %s"
|
||||||
msgstr "获取登录 URL 失败: %s"
|
msgstr "获取登录 URL 失败: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:859
|
||||||
msgid "Failed to get login URL: Invalid response from server."
|
|
||||||
msgstr "获取登录 URL 失败: 服务器响应无效。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609
|
|
||||||
msgid "Failed to load logs: %s"
|
msgid "Failed to load logs: %s"
|
||||||
msgstr "加载日志失败"
|
msgstr "加载日志失败: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:487
|
||||||
msgid "Filter peers by name, IP, or OS..."
|
msgid "Filter peers by name, IP, or OS..."
|
||||||
msgstr "按名称、IP 或操作系统过滤..."
|
msgstr "按名称、IP 或操作系统过滤..."
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
|
||||||
msgid "Filter:"
|
msgid "Filter:"
|
||||||
msgstr "过滤:"
|
msgstr "过滤:"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:54
|
||||||
msgid "Firewall Mode"
|
msgid "Firewall Mode"
|
||||||
msgstr "防火墙模式"
|
msgstr "防火墙模式"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:676
|
||||||
|
msgid "Firewall configuration already applied."
|
||||||
|
msgstr "防火墙配置已应用。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:159
|
||||||
msgid "Firewall configuration applied."
|
msgid "Firewall configuration applied."
|
||||||
msgstr "已应用防火墙配置"
|
msgstr "已应用防火墙配置"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:401
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:165
|
||||||
|
msgid "Firewall reload failed"
|
||||||
|
msgstr "重新加载防火墙失败"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:600
|
||||||
msgid "General Settings"
|
msgid "General Settings"
|
||||||
msgstr "常规设置"
|
msgstr "常规设置"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:449
|
||||||
msgid "Hostname"
|
msgid "Hostname"
|
||||||
msgstr "主机名"
|
msgstr "主机名"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:464
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:706
|
||||||
|
msgid "How to enable Exit Node?"
|
||||||
|
msgstr "如何启用出口节点?"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:688
|
||||||
msgid "How to enable Site-to-Site?"
|
msgid "How to enable Site-to-Site?"
|
||||||
msgstr "如何实现站点到站点(即多局域网互通)?"
|
msgstr "如何实现站点到站点(即多局域网互通)?"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:483
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:730
|
||||||
msgid ""
|
msgid ""
|
||||||
"If the timeout is displayed, you can refresh the page and click Login again."
|
"If the timeout is displayed, you can refresh the page and click Login "
|
||||||
|
"again."
|
||||||
msgstr "如果显示超时,您可以刷新页面并再次点击登录。"
|
msgstr "如果显示超时,您可以刷新页面并再次点击登录。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:497
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:744
|
||||||
msgid ""
|
msgid ""
|
||||||
"If you are using custom login server but not providing an Auth Key, will "
|
"If you are using custom login server but not providing an Auth Key, "
|
||||||
"redirect to the login page without pre-filling the key."
|
"will redirect to the login page without pre-filling the key."
|
||||||
msgstr ""
|
msgstr "如果您使用自定义登录服务器但未提供认证密钥,将重定向到登录页面而不会预先填充密钥。"
|
||||||
"如果您使用自定义登录服务器但未提供认证密钥,将重定向到登录页面而不会预先填充"
|
|
||||||
"密钥。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:93
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:215
|
||||||
msgid "Invalid Date"
|
msgid "Invalid Date"
|
||||||
msgstr "无效日期"
|
msgstr "无效日期"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:447
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:789
|
||||||
msgid ""
|
msgid "Invalid response from server."
|
||||||
"It automatically creates the tailscale interface, sets up firewall zones for "
|
msgstr "服务器响应无效。"
|
||||||
"LAN <-> Tailscale forwarding,"
|
|
||||||
msgstr "将自动创建Tailscale接口并设置防火墙区域用于LAN <-> Tailscale转发"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:96
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:646
|
||||||
|
msgid ""
|
||||||
|
"It creates the tailscale interface, adds it to a firewall zone, and "
|
||||||
|
"sets up forwarding between LAN and Tailscale, plus Tailscale to WAN."
|
||||||
|
msgstr ""
|
||||||
|
"它会创建 tailscale 接口,将其加入防火墙区域,并设置 LAN 与 Tailscale 之间的转发,以及 Tailscale 到 "
|
||||||
|
"WAN 的转发。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218
|
||||||
msgid "Just now"
|
msgid "Just now"
|
||||||
msgstr "刚才"
|
msgstr "刚才"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:208
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:330
|
||||||
msgid "LOGGED OUT"
|
msgid "LOGGED OUT"
|
||||||
msgstr "已登出"
|
msgstr "已登出"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:284
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
|
||||||
msgid "Last Seen"
|
msgid "Last Seen"
|
||||||
msgstr "上次在线"
|
msgstr "上次在线"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:489
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:736
|
||||||
msgid "Leave blank for default Tailscale control plane."
|
msgid "Leave blank for default Tailscale control plane."
|
||||||
msgstr "留空以使用默认的 Tailscale 控制平面。"
|
msgstr "留空以使用默认的 Tailscale 控制平面。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:853
|
||||||
|
msgid "Loading logs"
|
||||||
|
msgstr "正在加载日志"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:812
|
||||||
msgid "Logging out..."
|
msgid "Logging out..."
|
||||||
msgstr "正在登出..."
|
msgstr "正在登出..."
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:481
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:728
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "登录"
|
msgstr "登录"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:502
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:749
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:575
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:815
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:823
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr "登出"
|
msgstr "登出"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:820
|
||||||
msgid "Logout failed: %s"
|
msgid "Logout failed: %s"
|
||||||
msgstr "登出失败: %s"
|
msgstr "登出失败: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:587
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:836
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr "日志"
|
msgstr "日志"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "MagicDNS: Tailscale overrides resolv.conf."
|
msgid "MagicDNS: Tailscale overrides resolv.conf."
|
||||||
msgstr "MagicDNS:Tailscale将根据设置覆写系统resolv.conf配置"
|
msgstr "MagicDNS:Tailscale 会覆写 resolv.conf。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:648
|
||||||
|
msgid ""
|
||||||
|
"Masquerading and MSS Clamping (MTU fix) are enabled to keep "
|
||||||
|
"connections stable."
|
||||||
|
msgstr "会启用伪装和 MSS Clamping (MTU 修复),以保持连接稳定。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:212
|
||||||
msgid "N/A"
|
msgid "N/A"
|
||||||
msgstr "不可用"
|
msgstr "不可用"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:219
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:341
|
||||||
msgid "NOT RUNNING"
|
msgid "NOT RUNNING"
|
||||||
msgstr "未运行"
|
msgstr "未运行"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:273
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:439
|
||||||
|
msgid "No matching peer devices found."
|
||||||
|
msgstr "未找到匹配的节点设备。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
|
||||||
msgid "No peer devices found."
|
msgid "No peer devices found."
|
||||||
msgstr "未找到节点设备。"
|
msgstr "未找到节点设备。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:246
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:368
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:840
|
||||||
msgid "No tailscale-related logs found."
|
msgid "No tailscale-related logs found."
|
||||||
msgstr "没有找到Tailscale相关日志"
|
msgstr "没有找到Tailscale相关日志"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:414
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:613
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "无"
|
msgstr "无"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:91
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:213
|
||||||
msgid "Now"
|
msgid "Now"
|
||||||
msgstr "现在"
|
msgstr "现在"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:280
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
|
||||||
msgid "OS"
|
msgid "OS"
|
||||||
msgstr "操作系统"
|
msgstr "操作系统"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
msgid "Offline"
|
msgid "Offline"
|
||||||
msgstr "离线"
|
msgstr "离线"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
msgid "Online"
|
msgid "Online"
|
||||||
msgstr "在线"
|
msgstr "在线"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "OpenWrt Forward"
|
msgid "OpenWrt Forward"
|
||||||
msgstr "OpenWrt 转发"
|
msgstr "OpenWrt 转发"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)"
|
msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)"
|
||||||
msgstr "OpenWrt 转发:通过dnsmasq转发MagicDNS(仅支持 ts.net域名)。"
|
msgstr "OpenWrt 转发:通过dnsmasq转发MagicDNS(仅支持 ts.net域名)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:39
|
||||||
|
msgid "Operation"
|
||||||
|
msgstr "操作"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:735
|
||||||
msgid ""
|
msgid ""
|
||||||
"Optional: Specify a custom control server URL (e.g., a Headscale instance, "
|
"Optional: Specify a custom control server URL (e.g., a Headscale "
|
||||||
"%s)."
|
"instance, %s)."
|
||||||
msgstr "可选:指定一个自定义控制服务器 URL (例如,一个 Headscale 实例, %s)。"
|
msgstr "可选:指定一个自定义控制服务器 URL (例如,一个 Headscale 实例, %s)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:743
|
||||||
msgid ""
|
msgid ""
|
||||||
"Optional: Specify an authentication key for the custom control server. Leave "
|
"Optional: Specify an authentication key for the custom control "
|
||||||
"blank if not required."
|
"server. Leave blank if not required."
|
||||||
msgstr "可选:为自定义控制服务器指定一个认证密钥。如果不需要,请留空。"
|
msgstr "可选:为自定义控制服务器指定一个认证密钥。如果不需要,请留空。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:405
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:604
|
||||||
msgid "Peer Relay Port"
|
msgid "Peer Relay Port"
|
||||||
msgstr "DERP端口"
|
msgstr "节点中继端口"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:210
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:332
|
||||||
msgid "Please use the login button in the settings below to authenticate."
|
msgid "Please use the login button in the settings below to authenticate."
|
||||||
msgstr "请使用下方设置中的登录按钮进行认证。"
|
msgstr "请使用下方设置中的登录按钮进行认证。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:772
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:778
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:812
|
||||||
msgid "Please wait."
|
msgid "Please wait."
|
||||||
msgstr "请稍候。"
|
msgstr "请稍候。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:349
|
||||||
msgid "RUNNING"
|
msgid "RUNNING"
|
||||||
msgstr "正在运行"
|
msgstr "正在运行"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:282
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:453
|
||||||
msgid "RX"
|
msgid "RX"
|
||||||
msgstr "接收"
|
msgstr "接收"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:596
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:845
|
||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "刷新"
|
msgstr "刷新"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:177
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531
|
msgid "Reloading firewall..."
|
||||||
|
msgstr "正在重新加载防火墙..."
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:772
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:778
|
||||||
msgid "Requesting Login URL..."
|
msgid "Requesting Login URL..."
|
||||||
msgstr "正在请求登录 URL..."
|
msgstr "正在请求登录 URL..."
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:769
|
||||||
msgid "Requesting Tailscale login URL... Please wait."
|
msgid "Requesting Tailscale login URL... Please wait."
|
||||||
msgstr "正在请求 Tailscale 登录 URL... 请稍候。"
|
msgstr "正在请求 Tailscale 登录 URL... 请稍候。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:612
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select an exit node from the list. If enabled, Allow LAN Access is enabled "
|
"Select an exit node from the list. If enabled, Allow LAN Access is "
|
||||||
"implicitly."
|
"enabled implicitly."
|
||||||
msgstr "列表中选择出口节点。一旦开启,默认局域网之间可以互访"
|
msgstr "从列表中选择出口节点。启用后会同时开启允许局域网访问。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:54
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select the firewall backend for Tailscale to use. Requires service restart "
|
"Select the firewall backend for Tailscale to use. Requires service "
|
||||||
"to take effect."
|
"restart to take effect."
|
||||||
msgstr "选择 Tailscale 使用的防火墙后端。需要重启服务才能生效。"
|
msgstr "选择 Tailscale 使用的防火墙后端。需要重启服务才能生效。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:198
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:320
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:206
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:328
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:340
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:349
|
||||||
msgid "Service Status"
|
msgid "Service Status"
|
||||||
msgstr "服务状态"
|
msgstr "服务状态"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:70
|
||||||
msgid "Shields Up"
|
msgid ""
|
||||||
msgstr "开启防护 (Shields Up)"
|
"Set a custom MTU for the Tailscale daemon. Leave blank to use the "
|
||||||
|
"default value."
|
||||||
|
msgstr "为 Tailscale 守护进程设置自定义 MTU。留空则使用默认值。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:277
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:60
|
||||||
|
msgid "Shields Up"
|
||||||
|
msgstr "屏蔽入站连接"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "状态"
|
msgstr "状态"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:817
|
||||||
msgid "Successfully logged out."
|
msgid "Successfully logged out."
|
||||||
msgstr "登出成功。"
|
msgstr "登出成功。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:199
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:321
|
||||||
msgid "TAILSCALE NOT FOUND"
|
msgid "TAILSCALE NOT FOUND"
|
||||||
msgstr "未找到 TAILSCALE 主程序"
|
msgstr "未找到 TAILSCALE 主程序"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "TUN Mode"
|
msgid "TUN Mode"
|
||||||
msgstr "TUN 模式"
|
msgstr "TUN 模式"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:283
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:454
|
||||||
msgid "TX"
|
msgid "TX"
|
||||||
msgstr "发送"
|
msgstr "发送"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:232
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:354
|
||||||
msgid "Tailnet Name"
|
msgid "Tailnet Name"
|
||||||
msgstr "Tailnet 名称"
|
msgstr "Tailnet 名称"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:562
|
||||||
#: applications/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:3
|
#: luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:1
|
||||||
msgid "Tailscale"
|
msgid "Tailscale"
|
||||||
msgstr "Tailscale"
|
msgstr "Tailscale"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:175
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:297
|
||||||
msgid "Tailscale Health Check: %s"
|
msgid "Tailscale Health Check: %s"
|
||||||
msgstr "Tailscale 健康检查: %s"
|
msgstr "Tailscale 健康检查: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:279
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:450
|
||||||
msgid "Tailscale IP"
|
msgid "Tailscale IP"
|
||||||
msgstr "Tailscale IP"
|
msgstr "Tailscale IP"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:230
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:352
|
||||||
msgid "Tailscale IPv4"
|
msgid "Tailscale IPv4"
|
||||||
msgstr "Tailscale IPv4"
|
msgstr "Tailscale IPv4"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:231
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:353
|
||||||
msgid "Tailscale IPv6"
|
msgid "Tailscale IPv6"
|
||||||
msgstr "Tailscale IPv6"
|
msgstr "Tailscale IPv6"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:521
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:768
|
||||||
msgid "Tailscale Login"
|
msgid "Tailscale Login"
|
||||||
msgstr "Tailscale 登录"
|
msgstr "Tailscale 登录"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:562
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tailscale is a mesh VPN solution that makes it easy to connect your devices "
|
"Tailscale is a mesh VPN solution that makes it easy to connect your "
|
||||||
"securely. This configuration page allows you to manage Tailscale settings on "
|
"devices securely. This configuration page allows you to manage "
|
||||||
"your OpenWrt device."
|
"Tailscale settings on your OpenWrt device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Tailscale 是一个网状 VPN 解决方案,可以轻松地安全连接您的设备。此配置页面允许"
|
"Tailscale 是一个网状 VPN 解决方案,可以轻松地安全连接您的设备。此配置页面允许您在 OpenWrt 设备上管理 "
|
||||||
"您在 OpenWrt 设备上管理 Tailscale 设置。"
|
"Tailscale 设置。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:264
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:389
|
||||||
msgid "Tailscale status error"
|
msgid "Tailscale status error"
|
||||||
msgstr "Tailscale 状态异常"
|
msgstr "Tailscale 状态异常"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:523
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:647
|
||||||
|
msgid ""
|
||||||
|
"The Tailscale to WAN forwarding is what lets clients send their "
|
||||||
|
"internet traffic through this device as an Exit Node."
|
||||||
|
msgstr "正是 Tailscale 到 WAN 的转发,让客户端可以把上网流量经由此设备作为出口节点发出。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:770
|
||||||
msgid "This can take up to 30 seconds."
|
msgid "This can take up to 30 seconds."
|
||||||
msgstr "此过程最多可能需要 30 秒。"
|
msgstr "此过程最多可能需要 30 秒。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:553
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:800
|
||||||
msgid ""
|
msgid ""
|
||||||
"This will disconnect this device from your Tailnet and require you to re-"
|
"This will disconnect this device from your Tailnet and require you to "
|
||||||
"authenticate."
|
"re-authenticate."
|
||||||
msgstr "这将使此设备从您的 Tailnet 断开连接,并需要您重新进行身份验证。"
|
msgstr "这将使此设备从您的 Tailnet 断开连接,并需要您重新进行身份验证。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:406
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:143
|
||||||
msgid ""
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:194
|
||||||
"UDP port for the Peer Relay service. Open this port on your router firewall/"
|
msgid "Timed out waiting for the firewall reload"
|
||||||
"NAT."
|
msgstr "等待防火墙重新加载超时"
|
||||||
msgstr "DERP的UDP端口号。需要在防火墙/NAT规则中开放该端口。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:605
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
|
msgid ""
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153
|
"UDP port for the Peer Relay service. Open this port on your router "
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546
|
"firewall/NAT."
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572
|
msgstr "节点中继服务的 UDP 端口。需要在路由器的防火墙/NAT 上开放该端口。"
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:272
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:275
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:683
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:793
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:820
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:859
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
msgstr "未知错误"
|
msgstr "未知错误"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:350
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr "版本"
|
msgstr "版本"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:60
|
||||||
msgid ""
|
msgid ""
|
||||||
"When enabled, blocks all inbound connections from the Tailscale network."
|
"When enabled, blocks all inbound connections from the Tailscale "
|
||||||
|
"network."
|
||||||
msgstr "启用后,将阻止来自 Tailscale 网络的所有入站连接。"
|
msgstr "启用后,将阻止来自 Tailscale 网络的所有入站连接。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:57
|
||||||
msgid "When using the exit node, access to the local LAN is allowed."
|
msgid "When using the exit node, access to the local LAN is allowed."
|
||||||
msgstr "使用出口节点时,允许访问本地局域网。"
|
msgstr "使用出口节点时,允许访问本地局域网。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:472
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:660
|
||||||
|
msgid ""
|
||||||
|
"You have unapplied changes. Save & Apply them first so the firewall "
|
||||||
|
"matches the form."
|
||||||
|
msgstr "您有未应用的更改。请先保存并应用,让防火墙配置与表单保持一致。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:696
|
||||||
msgid ""
|
msgid ""
|
||||||
"[Important] Log in to the Tailscale admin console and manually enable "
|
"[Important] Log in to the Tailscale admin console and manually enable "
|
||||||
"\"Subnet Routes\" for this device."
|
"\"Subnet Routes\" for this device."
|
||||||
msgstr "【重要】 登录 Tailscale 控制平面,在设备设置中手动授权子网路由。"
|
msgstr "【重要】登录 Tailscale 管理控制台,手动为此设备启用“子网路由”。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:99
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:719
|
||||||
|
msgid ""
|
||||||
|
"[Important] Step 3 cannot be done here: it needs your Tailscale "
|
||||||
|
"account."
|
||||||
|
msgstr "【重要】第 3 步无法在此完成:需要您的 Tailscale 账户。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:221
|
||||||
msgid "ago"
|
msgid "ago"
|
||||||
msgstr "前"
|
msgstr "前"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448
|
|
||||||
msgid ""
|
|
||||||
"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable "
|
|
||||||
"connections."
|
|
||||||
msgstr "并启用伪装和MSS钳制(MTU修复)功能,以确保连接稳定。"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Completely disable all Tailscale firewall auto-configuration, including "
|
#~ "Completely disable all Tailscale firewall auto-configuration, "
|
||||||
#~ "zone creation, forwarding rules, and netfilter settings."
|
#~ "including zone creation, forwarding rules, and netfilter settings."
|
||||||
#~ msgstr ""
|
#~ msgstr "完全禁用所有 Tailscale 防火墙自动配置,包括区域创建、转发规则和 netfilter 设置。"
|
||||||
#~ "完全禁用所有 Tailscale 防火墙自动配置,包括区域创建、转发规则和 netfilter "
|
|
||||||
#~ "设置。"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Optional: Specify a custom control server URL (e.g., a Headscale "
|
#~ "Optional: Specify a custom control server URL (e.g., a Headscale "
|
||||||
#~ "instance, https://example.com)."
|
#~ "instance, https://example.com)."
|
||||||
#~ msgstr ""
|
#~ msgstr "可选:指定一个自定义控制服务器 URL (例如,一个 Headscale 实例,https://example.com)。"
|
||||||
#~ "可选:指定一个自定义控制服务器 URL (例如,一个 Headscale 实例,https://"
|
|
||||||
#~ "example.com)。"
|
|
||||||
|
|
||||||
#~ msgid "Applying changes..."
|
#~ msgid "Applying changes..."
|
||||||
#~ msgstr "正在应用更改..."
|
#~ msgstr "正在应用更改..."
|
||||||
@@ -601,3 +709,24 @@ msgstr "并启用伪装和MSS钳制(MTU修复)功能,以确保连接稳定
|
|||||||
|
|
||||||
#~ msgid "Tailscale settings applied successfully."
|
#~ msgid "Tailscale settings applied successfully."
|
||||||
#~ msgstr "Tailscale 设置已成功应用。"
|
#~ msgstr "Tailscale 设置已成功应用。"
|
||||||
|
|
||||||
|
#~ msgid "Failed to get login URL: Invalid response from server."
|
||||||
|
#~ msgstr "获取登录 URL 失败: 服务器响应无效。"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "It automatically creates the tailscale interface, sets up firewall "
|
||||||
|
#~ "zones for LAN <-> Tailscale forwarding,"
|
||||||
|
#~ msgstr "将自动创建Tailscale接口并设置防火墙区域用于LAN <-> Tailscale转发"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "and enables Masquerading and MSS Clamping (MTU fix) to ensure stable "
|
||||||
|
#~ "connections."
|
||||||
|
#~ msgstr "并启用伪装和MSS钳制(MTU修复)功能,以确保连接稳定。"
|
||||||
|
|
||||||
|
#~ msgid "4. On the client, choose this device under \"Exit node\"."
|
||||||
|
#~ msgstr "4. 在客户端中,在 \"出口节点\" 下选择此设备。"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "It creates the tailscale interface, adds it to a firewall zone, and "
|
||||||
|
#~ "sets up forwarding between LAN, Tailscale and WAN."
|
||||||
|
#~ msgstr "它会创建 tailscale 接口,将其加入防火墙区域,并在 LAN、Tailscale 和 WAN 之间设置转发。"
|
||||||
@@ -1,583 +1,693 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:47
|
||||||
|
msgid "%s failed"
|
||||||
|
msgstr "%s 失敗"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:71
|
||||||
msgid "(Experimental) Reduce Memory Usage"
|
msgid "(Experimental) Reduce Memory Usage"
|
||||||
msgstr "(實驗性) 減少記憶體使用"
|
msgstr "(實驗性) 減少記憶體使用"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:711
|
||||||
|
msgid "1. Enable \"Advertise Exit Node\"."
|
||||||
|
msgstr "1. 啟用「宣告出口節點」。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:693
|
||||||
msgid "1. Select \"Accept Routes\" (to access remote devices)."
|
msgid "1. Select \"Accept Routes\" (to access remote devices)."
|
||||||
msgstr "1. 勾選「接受路由」(以存取遠端設備)。"
|
msgstr "1. 勾選「接受路由」(以存取遠端設備)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:712
|
||||||
|
msgid ""
|
||||||
|
"2. Click \"Auto Configure Firewall\" (it forwards Tailscale traffic "
|
||||||
|
"to WAN)."
|
||||||
|
msgstr "2. 點選「自動設定防火牆」(將 Tailscale 流量轉送到 WAN)。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:694
|
||||||
msgid ""
|
msgid ""
|
||||||
"2. In \"Advertise Routes\", select your local subnet (to allow remote "
|
"2. In \"Advertise Routes\", select your local subnet (to allow remote "
|
||||||
"devices to access this LAN)."
|
"devices to access this LAN)."
|
||||||
msgstr "2. 在「宣告路由」選擇本裝置的區域網段 (讓遠端能存取這個區網)。"
|
msgstr "2. 在「宣告路由」選擇本裝置的區域網段 (讓遠端能存取這個區網)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:471
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:695
|
||||||
msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
|
msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
|
||||||
msgstr "3. 點選「自動配置防火牆」(允許流量轉送)。"
|
msgstr "3. 點選「自動設定防火牆」(允許流量轉送)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:713
|
||||||
|
msgid ""
|
||||||
|
"3. In the Tailscale admin console, edit this device under the route "
|
||||||
|
"settings and approve it as an Exit Node."
|
||||||
|
msgstr "3. 在 Tailscale 管理控制台的路由設定中編輯此裝置,並核准為出口節點。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:718
|
||||||
|
msgid "4. On the client, set this device as its exit node."
|
||||||
|
msgstr "4. 在用戶端上,將此裝置設為出口節點。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:55
|
||||||
msgid "Accept Routes"
|
msgid "Accept Routes"
|
||||||
msgstr "接受路由"
|
msgstr "接受路由"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:725
|
||||||
msgid "Account Settings"
|
msgid "Account Settings"
|
||||||
msgstr "帳號設定"
|
msgstr "帳號設定"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:56
|
||||||
msgid "Advertise Exit Node"
|
msgid "Advertise Exit Node"
|
||||||
msgstr "宣告出口節點"
|
msgstr "宣告出口節點"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:636
|
||||||
msgid "Advertise Routes"
|
msgid "Advertise Routes"
|
||||||
msgstr "宣告路由"
|
msgstr "宣告路由"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:636
|
||||||
msgid ""
|
msgid ""
|
||||||
"Advertise subnet routes behind this device. Select from the detected subnets "
|
"Advertise subnet routes behind this device. Select from the detected "
|
||||||
"below or enter custom routes (comma-separated)."
|
"subnets below or enter custom routes (comma-separated)."
|
||||||
msgstr ""
|
msgstr "宣告此裝置後方的子網路路由。從下方偵測到的子網路中選擇,或輸入自訂路由 (以逗號分隔)。"
|
||||||
"宣告此裝置後的子網路由。從下面的子網中選擇,或輸入自定義路由 (逗號分隔)。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:57
|
||||||
msgid "Allow LAN Access"
|
msgid "Allow LAN Access"
|
||||||
msgstr "允許區網存取"
|
msgstr "允許 LAN 存取"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:55
|
||||||
msgid "Allow accepting routes announced by other nodes."
|
msgid "Allow accepting routes announced by other nodes."
|
||||||
msgstr "允許接受由其他節點宣告的路由。"
|
msgstr "允許接受由其他節點宣告的路由。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:61
|
||||||
msgid "Allow connecting to this device through the SSH function of Tailscale."
|
msgid "Allow connecting to this device through the SSH function of Tailscale."
|
||||||
msgstr "允許透過 Tailscale 的 SSH 功能連線到此裝置。"
|
msgstr "允許透過 Tailscale 的 SSH 功能連線到此裝置。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:3
|
#: luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:1
|
||||||
msgid "Allow user access to tailscale"
|
msgid "Allow user access to tailscale"
|
||||||
msgstr "允許使用者存取 Tailscale"
|
msgstr "允許使用者存取 Tailscale"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:552
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:170
|
||||||
|
msgid "Another firewall reload is already running"
|
||||||
|
msgstr "另一個防火牆重新載入作業正在執行中"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:799
|
||||||
msgid "Are you sure you want to log out?"
|
msgid "Are you sure you want to log out?"
|
||||||
msgstr "您確定要登出嗎?"
|
msgstr "您確定要登出嗎?"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:644
|
||||||
msgid "Auto Configure Firewall"
|
msgid "Auto Configure Firewall"
|
||||||
msgstr "自動設定防火牆"
|
msgstr "自動設定防火牆"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:559
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:806
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "取消"
|
msgstr "取消"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:750
|
||||||
msgid "Click to Log out account on this device."
|
msgid "Click to Log out account on this device."
|
||||||
msgstr "點選以登出此裝置上的帳號。"
|
msgstr "點選以登出此裝置上的帳號。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:482
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:729
|
||||||
msgid "Click to get a login URL for this device."
|
msgid "Click to get a login URL for this device."
|
||||||
msgstr "點選獲取此裝置的登入 URL。"
|
msgstr "點選獲取此裝置的登入 URL。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:169
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:291
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:383
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:392
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:601
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:850
|
||||||
msgid "Collecting data ..."
|
msgid "Collecting data ..."
|
||||||
msgstr "正在收集資料..."
|
msgstr "正在收集資料..."
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:680
|
||||||
|
msgid ""
|
||||||
|
"Configuration committed, reloading network and firewall. This can "
|
||||||
|
"take a moment."
|
||||||
|
msgstr "設定已提交,正在重新載入網路與防火牆。這可能需要一點時間。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:673
|
||||||
|
msgid "Configuring firewall"
|
||||||
|
msgstr "正在設定防火牆"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:826
|
||||||
msgid "Confirm Logout"
|
msgid "Confirm Logout"
|
||||||
msgstr "確認登出"
|
msgstr "確認登出"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:281
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
|
||||||
msgid "Connection Info"
|
msgid "Connection Info"
|
||||||
msgstr "連線資訊"
|
msgstr "連線資訊"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "Controls how Tailscale DNS is handled."
|
msgid "Controls how Tailscale DNS is handled."
|
||||||
msgstr "控制 Tailscale DNS 的處理方式。"
|
msgstr "控制 Tailscale DNS 的處理方式。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:762
|
||||||
msgid ""
|
msgid ""
|
||||||
"Could not open a new tab. Please check if your browser or an extension "
|
"Could not open a new tab. Please check if your browser or an "
|
||||||
"blocked the pop-up."
|
"extension blocked the pop-up."
|
||||||
msgstr "無法開啟新分頁。請檢查您的瀏覽器或擴充套件程式是否阻止了彈出式視窗。"
|
msgstr "無法開啟新分頁。請檢查您的瀏覽器或擴充套件程式是否阻止了彈出式視窗。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:487
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:734
|
||||||
msgid "Custom Login Server"
|
msgid "Custom Login Server"
|
||||||
msgstr "自訂登入伺服器"
|
msgstr "自訂登入伺服器"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:742
|
||||||
msgid "Custom Login Server Auth Key"
|
msgid "Custom Login Server Auth Key"
|
||||||
msgstr "自訂登入伺服器認證金鑰"
|
msgstr "自訂登入伺服器認證金鑰"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "DNS Mode"
|
msgid "DNS Mode"
|
||||||
msgstr "DNS 模式"
|
msgstr "DNS 模式"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:70
|
||||||
|
msgid "Daemon MTU"
|
||||||
|
msgstr "常駐程式 MTU"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:865
|
||||||
|
msgid "Daemon Settings"
|
||||||
|
msgstr "常駐程式設定"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:56
|
||||||
msgid "Declare this device as an Exit Node."
|
msgid "Declare this device as an Exit Node."
|
||||||
msgstr "將此裝置宣告為出口節點。"
|
msgstr "將此裝置宣告為出口節點。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:581
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:829
|
||||||
msgid "Devices List"
|
msgid "Devices List"
|
||||||
msgstr "裝置清單"
|
msgstr "裝置清單"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:63
|
||||||
msgid "Disable Firewall Configuration"
|
msgid "Disable Firewall Configuration"
|
||||||
msgstr "停用防火牆設定"
|
msgstr "停用防火牆設定"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:59
|
||||||
msgid "Disable SNAT"
|
msgid "Disable SNAT"
|
||||||
msgstr "停用 SNAT"
|
msgstr "停用 SNAT"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:59
|
||||||
msgid ""
|
msgid ""
|
||||||
"Disable Source NAT (SNAT) for traffic to advertised routes. Most users "
|
"Disable Source NAT (SNAT) for traffic to advertised routes. Most "
|
||||||
"should leave this unchecked."
|
"users should leave this unchecked."
|
||||||
msgstr "為宣告路由的流量停用來源位址轉換 (SNAT)。大多數使用者不應勾選此項。"
|
msgstr "為宣告路由的流量停用來源位址轉換 (SNAT)。大多數使用者不應勾選此項。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:63
|
||||||
msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)."
|
msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)."
|
||||||
msgstr "停用 Tailscale netfilter 自動設定 (--netfilter-mode=off)。"
|
msgstr "停用 Tailscale netfilter 自動設定 (--netfilter-mode=off)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "Disabled"
|
msgid "Disabled"
|
||||||
msgstr "已停用"
|
msgstr "已停用"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "Disabled: system DNS only."
|
msgid "Disabled: system DNS only."
|
||||||
msgstr "已停用: 僅使用系統 DNS。"
|
msgstr "已停用: 僅使用系統 DNS。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:751
|
||||||
msgid "Disconnect from Tailscale and expire current node key."
|
msgid "Disconnect from Tailscale and expire current node key."
|
||||||
msgstr "中斷 Tailscale 連線並使目前節點金鑰過期。"
|
msgstr "中斷 Tailscale 連線並使目前節點金鑰過期。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:41
|
||||||
|
msgid "Empty reply from server"
|
||||||
|
msgstr "伺服器回應為空"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:64
|
||||||
msgid "Enable Peer Relay"
|
msgid "Enable Peer Relay"
|
||||||
msgstr "啟用對等中繼"
|
msgstr "啟用對等中繼"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:61
|
||||||
msgid "Enable Tailscale SSH"
|
msgid "Enable Tailscale SSH"
|
||||||
msgstr "啟用 Tailscale SSH"
|
msgstr "啟用 Tailscale SSH"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:53
|
||||||
msgid "Enable Tailscale Service"
|
msgid "Enable Tailscale Service"
|
||||||
msgstr "啟用 Tailscale 服務"
|
msgstr "啟用 Tailscale 服務"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:58
|
||||||
msgid "Enable Web Interface"
|
msgid "Enable Web Interface"
|
||||||
msgstr "啟用 Web 介面"
|
msgstr "啟用 Web 介面"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:53
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enable or disable the Tailscale service. When disabled, the service will be "
|
"Enable or disable the Tailscale service. When disabled, the service "
|
||||||
"stopped and the process will be killed."
|
"will be stopped and the process will be killed."
|
||||||
msgstr "啟用或停用 Tailscale 服務。停用後,該服務將停止,處理程序將被終止。"
|
msgstr "啟用或停用 Tailscale 服務。停用後,該服務將停止,處理程序將被終止。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:64
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enable this device as a Peer Relay server. Requires a public IP and an UDP "
|
"Enable this device as a Peer Relay server. Requires a public IP and "
|
||||||
"port open on the router."
|
"an UDP port open on the router."
|
||||||
msgstr "將此設備做為對等中繼伺服器。需要公開 IP 位址,且路由器上需要開啟一個 UDP 連接埠。"
|
msgstr "將此設備做為對等中繼伺服器。需要公開 IP 位址,且路由器上需要開啟一個 UDP 連接埠。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr "已啟用"
|
msgstr "已啟用"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:71
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enabling this option can reduce memory usage, but it may sacrifice some "
|
"Enabling this option can reduce memory usage, but it may sacrifice "
|
||||||
"performance (set GOGC=10)."
|
"some performance (set GOGC=10)."
|
||||||
msgstr "啟用此選項可以減少記憶體使用,但可能會犧牲一些效能 (設定 GOGC=10)。"
|
msgstr "啟用此選項可以減少記憶體使用,但可能會犧牲一些效能 (設定 GOGC=10)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:540
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:787
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "錯誤"
|
msgstr "錯誤"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:272
|
||||||
msgid "Error caching DERP region map: %s"
|
msgid "Error caching DERP region map: %s"
|
||||||
msgstr "快取 DERP 區域對照表時出錯: %s"
|
msgstr "快取 DERP 區域對照表時出錯: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:275
|
||||||
msgid "Error fetching DERP region map: %s"
|
msgid "Error fetching DERP region map: %s"
|
||||||
msgstr "獲取 DERP 區域對照表時出錯: %s"
|
msgstr "獲取 DERP 區域對照表時出錯: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
|
||||||
msgid "Error reading cached DERP region map: %s"
|
msgid "Error reading cached DERP region map: %s"
|
||||||
msgstr "讀取快取的 DERP 區域對照表時出錯: %s"
|
msgstr "讀取快取的 DERP 區域對照表時出錯: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:645
|
||||||
msgid ""
|
msgid ""
|
||||||
"Essential configuration for Subnet Routing (Site-to-Site) and Exit Node "
|
"Essential configuration for Subnet Routing (Site-to-Site) and Exit "
|
||||||
"features."
|
"Node features."
|
||||||
msgstr "子網路由(站對站)和出口節點功能的基本設定"
|
msgstr "子網路由(站對站)和出口節點功能的基本設定"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:612
|
||||||
msgid "Exit Node"
|
msgid "Exit Node"
|
||||||
msgstr "出口節點"
|
msgstr "出口節點"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:58
|
||||||
msgid ""
|
msgid ""
|
||||||
"Expose a web interface on port 5252 for managing this node over Tailscale."
|
"Expose a web interface on port 5252 for managing this node over "
|
||||||
|
"Tailscale."
|
||||||
msgstr "在埠 5252 上暴露一個 Web 介面,用於透過 Tailscale 管理此節點。"
|
msgstr "在埠 5252 上暴露一個 Web 介面,用於透過 Tailscale 管理此節點。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:683
|
||||||
msgid "Failed to configure firewall: %s"
|
msgid "Failed to configure firewall: %s"
|
||||||
msgstr "獲取防火牆設定失敗: %s"
|
msgstr "設定防火牆失敗: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:788
|
||||||
msgid "Failed to get login URL. You may close this tab."
|
msgid "Failed to get login URL. You may close this tab."
|
||||||
msgstr "獲取登入 URL 失敗。您可以關閉此分頁。"
|
msgstr "獲取登入 URL 失敗。您可以關閉此分頁。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:789
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:793
|
||||||
msgid "Failed to get login URL: %s"
|
msgid "Failed to get login URL: %s"
|
||||||
msgstr "獲取登入 URL 失敗: %s"
|
msgstr "獲取登入 URL 失敗: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:859
|
||||||
msgid "Failed to get login URL: Invalid response from server."
|
|
||||||
msgstr "獲取登入 URL 失敗: 伺服器回應無效。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609
|
|
||||||
msgid "Failed to load logs: %s"
|
msgid "Failed to load logs: %s"
|
||||||
msgstr "載入日誌失敗:%s"
|
msgstr "載入日誌失敗: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:487
|
||||||
msgid "Filter peers by name, IP, or OS..."
|
msgid "Filter peers by name, IP, or OS..."
|
||||||
msgstr "依名稱、IP 或作業系統篩選..."
|
msgstr "依名稱、IP 或作業系統篩選..."
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
|
||||||
msgid "Filter:"
|
msgid "Filter:"
|
||||||
msgstr "篩選:"
|
msgstr "篩選:"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:54
|
||||||
msgid "Firewall Mode"
|
msgid "Firewall Mode"
|
||||||
msgstr "防火牆模式"
|
msgstr "防火牆模式"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:676
|
||||||
|
msgid "Firewall configuration already applied."
|
||||||
|
msgstr "防火牆設定已套用。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:159
|
||||||
msgid "Firewall configuration applied."
|
msgid "Firewall configuration applied."
|
||||||
msgstr "已套用防火牆設定"
|
msgstr "已套用防火牆設定"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:401
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:165
|
||||||
|
msgid "Firewall reload failed"
|
||||||
|
msgstr "防火牆重新載入失敗"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:600
|
||||||
msgid "General Settings"
|
msgid "General Settings"
|
||||||
msgstr "一般設定"
|
msgstr "一般設定"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:449
|
||||||
msgid "Hostname"
|
msgid "Hostname"
|
||||||
msgstr "主機名"
|
msgstr "主機名"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:464
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:706
|
||||||
|
msgid "How to enable Exit Node?"
|
||||||
|
msgstr "如何啟用出口節點?"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:688
|
||||||
msgid "How to enable Site-to-Site?"
|
msgid "How to enable Site-to-Site?"
|
||||||
msgstr "如何啟用站對站通訊?"
|
msgstr "如何啟用站對站通訊?"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:483
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:730
|
||||||
msgid ""
|
msgid ""
|
||||||
"If the timeout is displayed, you can refresh the page and click Login again."
|
"If the timeout is displayed, you can refresh the page and click Login "
|
||||||
|
"again."
|
||||||
msgstr "如果顯示逾時,您可以重新整理頁面並再次點選登入。"
|
msgstr "如果顯示逾時,您可以重新整理頁面並再次點選登入。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:497
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:744
|
||||||
msgid ""
|
msgid ""
|
||||||
"If you are using custom login server but not providing an Auth Key, will "
|
"If you are using custom login server but not providing an Auth Key, "
|
||||||
"redirect to the login page without pre-filling the key."
|
"will redirect to the login page without pre-filling the key."
|
||||||
msgstr ""
|
msgstr "如果您使用自訂登入伺服器但未提供認證金鑰,將重新導向至登入頁面,且不會預先填入金鑰。"
|
||||||
"如果您使用自定義登入伺服器但未提供認證金鑰,將重定向到登入頁面而不會預先填充"
|
|
||||||
"金鑰。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:93
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:215
|
||||||
msgid "Invalid Date"
|
msgid "Invalid Date"
|
||||||
msgstr "無效日期"
|
msgstr "無效日期"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:447
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:789
|
||||||
msgid ""
|
msgid "Invalid response from server."
|
||||||
"It automatically creates the tailscale interface, sets up firewall zones for "
|
msgstr "伺服器回應無效。"
|
||||||
"LAN <-> Tailscale forwarding,"
|
|
||||||
msgstr "它會自動建立 Tailscale 介面,並設定允許 LAN <-> Tailscale 防火牆區域轉送。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:96
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:646
|
||||||
|
msgid ""
|
||||||
|
"It creates the tailscale interface, adds it to a firewall zone, and "
|
||||||
|
"sets up forwarding between LAN and Tailscale, plus Tailscale to WAN."
|
||||||
|
msgstr ""
|
||||||
|
"它會建立 tailscale 介面、加入防火牆區域,並設定 LAN 與 Tailscale 之間的轉送,以及 Tailscale 到 "
|
||||||
|
"WAN 的轉送。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218
|
||||||
msgid "Just now"
|
msgid "Just now"
|
||||||
msgstr "剛才"
|
msgstr "剛才"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:208
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:330
|
||||||
msgid "LOGGED OUT"
|
msgid "LOGGED OUT"
|
||||||
msgstr "已登出"
|
msgstr "已登出"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:284
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
|
||||||
msgid "Last Seen"
|
msgid "Last Seen"
|
||||||
msgstr "上次連線"
|
msgstr "上次連線"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:489
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:736
|
||||||
msgid "Leave blank for default Tailscale control plane."
|
msgid "Leave blank for default Tailscale control plane."
|
||||||
msgstr "留白則使用預設的 Tailscale 控制台。"
|
msgstr "留白則使用預設的 Tailscale 控制台。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:853
|
||||||
|
msgid "Loading logs"
|
||||||
|
msgstr "正在載入日誌"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:812
|
||||||
msgid "Logging out..."
|
msgid "Logging out..."
|
||||||
msgstr "正在登出..."
|
msgstr "正在登出..."
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:481
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:728
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "登入"
|
msgstr "登入"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:502
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:749
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:575
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:815
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:823
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr "登出"
|
msgstr "登出"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:820
|
||||||
msgid "Logout failed: %s"
|
msgid "Logout failed: %s"
|
||||||
msgstr "登出失敗: %s"
|
msgstr "登出失敗: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:587
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:836
|
||||||
msgid "Logs"
|
msgid "Logs"
|
||||||
msgstr "日誌"
|
msgstr "日誌"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "MagicDNS: Tailscale overrides resolv.conf."
|
msgid "MagicDNS: Tailscale overrides resolv.conf."
|
||||||
msgstr "MagicDNS:Tailscale 將接管 resolv.conf。"
|
msgstr "MagicDNS:Tailscale 將接管 resolv.conf。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:648
|
||||||
|
msgid ""
|
||||||
|
"Masquerading and MSS Clamping (MTU fix) are enabled to keep "
|
||||||
|
"connections stable."
|
||||||
|
msgstr "同時啟用偽裝與 MSS Clamping (MTU 修復),以維持連線穩定。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:212
|
||||||
msgid "N/A"
|
msgid "N/A"
|
||||||
msgstr "N/A"
|
msgstr "N/A"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:219
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:341
|
||||||
msgid "NOT RUNNING"
|
msgid "NOT RUNNING"
|
||||||
msgstr "未執行"
|
msgstr "未執行"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:273
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:439
|
||||||
|
msgid "No matching peer devices found."
|
||||||
|
msgstr "找不到符合的對等裝置。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
|
||||||
msgid "No peer devices found."
|
msgid "No peer devices found."
|
||||||
msgstr "未找到對等裝置。"
|
msgstr "未找到對等裝置。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:246
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:368
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:840
|
||||||
msgid "No tailscale-related logs found."
|
msgid "No tailscale-related logs found."
|
||||||
msgstr "未找到與 tailscale 相關的日誌。"
|
msgstr "未找到與 tailscale 相關的日誌。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:414
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:613
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "無"
|
msgstr "無"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:91
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:213
|
||||||
msgid "Now"
|
msgid "Now"
|
||||||
msgstr "現在"
|
msgstr "現在"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:280
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
|
||||||
msgid "OS"
|
msgid "OS"
|
||||||
msgstr "作業系統"
|
msgstr "作業系統"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
msgid "Offline"
|
msgid "Offline"
|
||||||
msgstr "離線"
|
msgstr "離線"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:424
|
||||||
msgid "Online"
|
msgid "Online"
|
||||||
msgstr "上線"
|
msgstr "上線"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "OpenWrt Forward"
|
msgid "OpenWrt Forward"
|
||||||
msgstr "OpenWrt 轉送"
|
msgstr "OpenWrt 轉送"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:62
|
||||||
msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)"
|
msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)"
|
||||||
msgstr "OpenWrt 轉送:僅經由 dnsmasq 轉送至 MagicDNS。(僅支援tailxxxxxx.ts.net)"
|
msgstr "OpenWrt 轉送:僅經由 dnsmasq 轉送至 MagicDNS。(僅支援tailxxxxxx.ts.net)"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:39
|
||||||
|
msgid "Operation"
|
||||||
|
msgstr "操作"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:735
|
||||||
msgid ""
|
msgid ""
|
||||||
"Optional: Specify a custom control server URL (e.g., a Headscale instance, "
|
"Optional: Specify a custom control server URL (e.g., a Headscale "
|
||||||
"%s)."
|
"instance, %s)."
|
||||||
msgstr "可選:指定自訂控制伺服器 URL(例如,Headscale 實例,「%s」)。"
|
msgstr "可選:指定自訂控制伺服器 URL(例如,Headscale 實例,「%s」)。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:743
|
||||||
msgid ""
|
msgid ""
|
||||||
"Optional: Specify an authentication key for the custom control server. Leave "
|
"Optional: Specify an authentication key for the custom control "
|
||||||
"blank if not required."
|
"server. Leave blank if not required."
|
||||||
msgstr "可選:為自訂控制伺服器指定一個認證金鑰。如果不需要,請留白。"
|
msgstr "可選:為自訂控制伺服器指定一個認證金鑰。如果不需要,請留白。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:405
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:604
|
||||||
msgid "Peer Relay Port"
|
msgid "Peer Relay Port"
|
||||||
msgstr "對等中繼連接埠"
|
msgstr "對等中繼連接埠"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:210
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:332
|
||||||
msgid "Please use the login button in the settings below to authenticate."
|
msgid "Please use the login button in the settings below to authenticate."
|
||||||
msgstr "請使用下方設定中的登入按鈕進行認證。"
|
msgstr "請使用下方設定中的登入按鈕進行認證。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:772
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:778
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:812
|
||||||
msgid "Please wait."
|
msgid "Please wait."
|
||||||
msgstr "請稍候。"
|
msgstr "請稍候。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:349
|
||||||
msgid "RUNNING"
|
msgid "RUNNING"
|
||||||
msgstr "正在執行"
|
msgstr "正在執行"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:282
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:453
|
||||||
msgid "RX"
|
msgid "RX"
|
||||||
msgstr "接收"
|
msgstr "接收"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:596
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:845
|
||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "重新整理"
|
msgstr "重新整理"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:177
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531
|
msgid "Reloading firewall..."
|
||||||
|
msgstr "正在重新載入防火牆..."
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:772
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:778
|
||||||
msgid "Requesting Login URL..."
|
msgid "Requesting Login URL..."
|
||||||
msgstr "正在請求登入 URL..."
|
msgstr "正在請求登入 URL..."
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:769
|
||||||
msgid "Requesting Tailscale login URL... Please wait."
|
msgid "Requesting Tailscale login URL... Please wait."
|
||||||
msgstr "正在請求 Tailscale 登入 URL... 請稍候。"
|
msgstr "正在請求 Tailscale 登入 URL... 請稍候。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:612
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select an exit node from the list. If enabled, Allow LAN Access is enabled "
|
"Select an exit node from the list. If enabled, Allow LAN Access is "
|
||||||
"implicitly."
|
"enabled implicitly."
|
||||||
msgstr "從清單中選擇一個出口節點。如果啟用,則會被動啟用‘允許區域網路存取’"
|
msgstr "從清單中選擇一個出口節點。啟用後會一併啟用「允許區網存取」。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:54
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select the firewall backend for Tailscale to use. Requires service restart "
|
"Select the firewall backend for Tailscale to use. Requires service "
|
||||||
"to take effect."
|
"restart to take effect."
|
||||||
msgstr "選擇 Tailscale 使用的防火牆後端。需要重新啟動服務才能生效。"
|
msgstr "選擇 Tailscale 使用的防火牆後端。需要重新啟動服務才能生效。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:198
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:320
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:206
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:328
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:340
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:349
|
||||||
msgid "Service Status"
|
msgid "Service Status"
|
||||||
msgstr "服務狀態"
|
msgstr "服務狀態"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:70
|
||||||
msgid "Shields Up"
|
msgid ""
|
||||||
msgstr "開啟防護 (Shields Up)"
|
"Set a custom MTU for the Tailscale daemon. Leave blank to use the "
|
||||||
|
"default value."
|
||||||
|
msgstr "為 Tailscale 常駐程式設定自訂 MTU。留白則使用預設值。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:277
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:60
|
||||||
|
msgid "Shields Up"
|
||||||
|
msgstr "封鎖入站連線"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "狀態"
|
msgstr "狀態"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:817
|
||||||
msgid "Successfully logged out."
|
msgid "Successfully logged out."
|
||||||
msgstr "登出成功。"
|
msgstr "登出成功。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:199
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:321
|
||||||
msgid "TAILSCALE NOT FOUND"
|
msgid "TAILSCALE NOT FOUND"
|
||||||
msgstr "找不到 TAILSCALE"
|
msgstr "找不到 TAILSCALE"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:351
|
||||||
msgid "TUN Mode"
|
msgid "TUN Mode"
|
||||||
msgstr "TUN 模式"
|
msgstr "TUN 模式"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:283
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:454
|
||||||
msgid "TX"
|
msgid "TX"
|
||||||
msgstr "傳送"
|
msgstr "傳送"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:232
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:354
|
||||||
msgid "Tailnet Name"
|
msgid "Tailnet Name"
|
||||||
msgstr "Tailnet 名稱"
|
msgstr "Tailnet 名稱"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:562
|
||||||
#: applications/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:3
|
#: luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:1
|
||||||
msgid "Tailscale"
|
msgid "Tailscale"
|
||||||
msgstr "Tailscale"
|
msgstr "Tailscale"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:175
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:297
|
||||||
msgid "Tailscale Health Check: %s"
|
msgid "Tailscale Health Check: %s"
|
||||||
msgstr "Tailscale 健康檢查: %s"
|
msgstr "Tailscale 健康檢查: %s"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:279
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:450
|
||||||
msgid "Tailscale IP"
|
msgid "Tailscale IP"
|
||||||
msgstr "Tailscale IP"
|
msgstr "Tailscale IP"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:230
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:352
|
||||||
msgid "Tailscale IPv4"
|
msgid "Tailscale IPv4"
|
||||||
msgstr "Tailscale IPv4"
|
msgstr "Tailscale IPv4"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:231
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:353
|
||||||
msgid "Tailscale IPv6"
|
msgid "Tailscale IPv6"
|
||||||
msgstr "Tailscale IPv6"
|
msgstr "Tailscale IPv6"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:521
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:768
|
||||||
msgid "Tailscale Login"
|
msgid "Tailscale Login"
|
||||||
msgstr "Tailscale 登入"
|
msgstr "Tailscale 登入"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:562
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tailscale is a mesh VPN solution that makes it easy to connect your devices "
|
"Tailscale is a mesh VPN solution that makes it easy to connect your "
|
||||||
"securely. This configuration page allows you to manage Tailscale settings on "
|
"devices securely. This configuration page allows you to manage "
|
||||||
"your OpenWrt device."
|
"Tailscale settings on your OpenWrt device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Tailscale 是一個網狀 VPN 解決方案,可以輕鬆地安全連線您的裝置。此設定頁面允許"
|
"Tailscale 是一個網狀 VPN 解決方案,可以輕鬆地安全連線您的裝置。此設定頁面允許您在 OpenWrt 裝置上管理 "
|
||||||
"您在 OpenWrt 裝置上管理 Tailscale 設定。"
|
"Tailscale 設定。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:264
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:389
|
||||||
msgid "Tailscale status error"
|
msgid "Tailscale status error"
|
||||||
msgstr "Tailscale 狀態錯誤"
|
msgstr "Tailscale 狀態錯誤"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:523
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:647
|
||||||
|
msgid ""
|
||||||
|
"The Tailscale to WAN forwarding is what lets clients send their "
|
||||||
|
"internet traffic through this device as an Exit Node."
|
||||||
|
msgstr "Tailscale 到 WAN 的轉送,讓用戶端能透過此裝置以出口節點傳送網際網路流量。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:770
|
||||||
msgid "This can take up to 30 seconds."
|
msgid "This can take up to 30 seconds."
|
||||||
msgstr "此過程最多可能需要 30 秒。"
|
msgstr "此過程最多可能需要 30 秒。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:553
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:800
|
||||||
msgid ""
|
msgid ""
|
||||||
"This will disconnect this device from your Tailnet and require you to re-"
|
"This will disconnect this device from your Tailnet and require you to "
|
||||||
"authenticate."
|
"re-authenticate."
|
||||||
msgstr "這將使此裝置從您的 Tailnet 中斷連線,並需要您重新進行身份驗證。"
|
msgstr "這將使此裝置從您的 Tailnet 中斷連線,並需要您重新進行身份驗證。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:406
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:143
|
||||||
msgid ""
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:194
|
||||||
"UDP port for the Peer Relay service. Open this port on your router firewall/"
|
msgid "Timed out waiting for the firewall reload"
|
||||||
"NAT."
|
msgstr "等待防火牆重新載入逾時"
|
||||||
msgstr "用於對等中繼服務的 UDP 連接埠。請在路由器防火牆上開啟此連接埠以及 NAT。"
|
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:605
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
|
msgid ""
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153
|
"UDP port for the Peer Relay service. Open this port on your router "
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546
|
"firewall/NAT."
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572
|
msgstr "用於對等中繼服務的 UDP 連接埠。請在路由器的防火牆/NAT 上開放此連接埠。"
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:272
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:275
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:683
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:793
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:820
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:859
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
msgstr "未知錯誤"
|
msgstr "未知錯誤"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:350
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr "版本"
|
msgstr "版本"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:60
|
||||||
msgid ""
|
msgid ""
|
||||||
"When enabled, blocks all inbound connections from the Tailscale network."
|
"When enabled, blocks all inbound connections from the Tailscale "
|
||||||
|
"network."
|
||||||
msgstr "啟用後,將阻止來自 Tailscale 網路的所有入站連線。"
|
msgstr "啟用後,將阻止來自 Tailscale 網路的所有入站連線。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:57
|
||||||
msgid "When using the exit node, access to the local LAN is allowed."
|
msgid "When using the exit node, access to the local LAN is allowed."
|
||||||
msgstr "使用出口節點時,允許存取區域網路。"
|
msgstr "使用出口節點時,允許存取區域網路。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:472
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:660
|
||||||
|
msgid ""
|
||||||
|
"You have unapplied changes. Save & Apply them first so the firewall "
|
||||||
|
"matches the form."
|
||||||
|
msgstr "您有尚未套用的變更。請先儲存並套用,防火牆才會與表單內容一致。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:696
|
||||||
msgid ""
|
msgid ""
|
||||||
"[Important] Log in to the Tailscale admin console and manually enable "
|
"[Important] Log in to the Tailscale admin console and manually enable "
|
||||||
"\"Subnet Routes\" for this device."
|
"\"Subnet Routes\" for this device."
|
||||||
msgstr "【重要】 登入 Tailscale 管理控制台,在裝置設定中手動授權子網路由。"
|
msgstr "【重要】 登入 Tailscale 管理控制台,在裝置設定中手動授權子網路由。"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:99
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:719
|
||||||
|
msgid ""
|
||||||
|
"[Important] Step 3 cannot be done here: it needs your Tailscale "
|
||||||
|
"account."
|
||||||
|
msgstr "[重要] 步驟 3 無法在此完成:需要您的 Tailscale 帳號。"
|
||||||
|
|
||||||
|
#: luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:221
|
||||||
msgid "ago"
|
msgid "ago"
|
||||||
msgstr "前"
|
msgstr "前"
|
||||||
|
|
||||||
#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448
|
|
||||||
msgid ""
|
|
||||||
"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable "
|
|
||||||
"connections."
|
|
||||||
msgstr "並啟用IP偽裝和 MSS 箝制(MTU 修復)以確保連線穩定。"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Completely disable all Tailscale firewall auto-configuration, including "
|
#~ "Completely disable all Tailscale firewall auto-configuration, "
|
||||||
#~ "zone creation, forwarding rules, and netfilter settings."
|
#~ "including zone creation, forwarding rules, and netfilter settings."
|
||||||
#~ msgstr ""
|
#~ msgstr "完全停用所有 Tailscale 防火牆自動設定,包括區域建立、轉送規則和 netfilter 設定。"
|
||||||
#~ "完全停用所有 Tailscale 防火牆自動設定,包括區域建立、轉送規則和 netfilter "
|
|
||||||
#~ "設定。"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Optional: Specify a custom control server URL (e.g., a Headscale "
|
#~ "Optional: Specify a custom control server URL (e.g., a Headscale "
|
||||||
#~ "instance, https://example.com)."
|
#~ "instance, https://example.com)."
|
||||||
#~ msgstr ""
|
#~ msgstr "可選:指定一個自定義控制伺服器 URL (例如,一個 Headscale 例項,https://example.com)。"
|
||||||
#~ "可選:指定一個自定義控制伺服器 URL (例如,一個 Headscale 例項,https://"
|
|
||||||
#~ "example.com)。"
|
|
||||||
|
|
||||||
#~ msgid "Applying changes..."
|
#~ msgid "Applying changes..."
|
||||||
#~ msgstr "正在套用更改..."
|
#~ msgstr "正在套用更改..."
|
||||||
@@ -599,3 +709,24 @@ msgstr "並啟用IP偽裝和 MSS 箝制(MTU 修復)以確保連線穩定。"
|
|||||||
|
|
||||||
#~ msgid "Tailscale settings applied successfully."
|
#~ msgid "Tailscale settings applied successfully."
|
||||||
#~ msgstr "Tailscale 設定已成功套用。"
|
#~ msgstr "Tailscale 設定已成功套用。"
|
||||||
|
|
||||||
|
#~ msgid "Failed to get login URL: Invalid response from server."
|
||||||
|
#~ msgstr "獲取登入 URL 失敗: 伺服器回應無效。"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "It automatically creates the tailscale interface, sets up firewall "
|
||||||
|
#~ "zones for LAN <-> Tailscale forwarding,"
|
||||||
|
#~ msgstr "它會自動建立 Tailscale 介面,並設定允許 LAN <-> Tailscale 防火牆區域轉送。"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "and enables Masquerading and MSS Clamping (MTU fix) to ensure stable "
|
||||||
|
#~ "connections."
|
||||||
|
#~ msgstr "並啟用IP偽裝和 MSS 箝制(MTU 修復)以確保連線穩定。"
|
||||||
|
|
||||||
|
#~ msgid "4. On the client, choose this device under \"Exit node\"."
|
||||||
|
#~ msgstr "4. 在用戶端於「出口節點」選擇此裝置。"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "It creates the tailscale interface, adds it to a firewall zone, and "
|
||||||
|
#~ "sets up forwarding between LAN, Tailscale and WAN."
|
||||||
|
#~ msgstr "它會建立 tailscale 介面、加入防火牆區域,並設定 LAN、Tailscale 與 WAN 之間的轉送。"
|
||||||
@@ -6,6 +6,15 @@ STOP=10
|
|||||||
|
|
||||||
NAME=tailscale-settings
|
NAME=tailscale-settings
|
||||||
|
|
||||||
|
# State files live on tmpfs so they are rebuilt after every boot.
|
||||||
|
STATE_DIR=/var/run/tailscale-settings
|
||||||
|
DAEMON_SIG_FILE="$STATE_DIR/daemon"
|
||||||
|
ENABLED_FILE="$STATE_DIR/enabled"
|
||||||
|
DISABLE_REQUEST_FILE="$STATE_DIR/disable-requested"
|
||||||
|
DNS_MODE_FILE="$STATE_DIR/dns_mode"
|
||||||
|
|
||||||
|
TAILSCALE_INIT=/etc/init.d/tailscale
|
||||||
|
|
||||||
# Compare version strings: return 0 (true) if $1 >= $2
|
# Compare version strings: return 0 (true) if $1 >= $2
|
||||||
version_gte() {
|
version_gte() {
|
||||||
awk -v a="${1%%-*}" -v b="${2%%-*}" 'BEGIN {
|
awk -v a="${1%%-*}" -v b="${2%%-*}" 'BEGIN {
|
||||||
@@ -21,6 +30,31 @@ version_gte() {
|
|||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Path of the tailscale daemon state file, as configured for the daemon init.
|
||||||
|
tailscale_state_file() {
|
||||||
|
local state_file
|
||||||
|
config_get state_file settings state_file '/etc/tailscale/tailscaled.state'
|
||||||
|
echo "$state_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fingerprint of the UCI options that are only read by the daemon init script
|
||||||
|
# (they end up as procd env/command arguments, so changing them needs a restart).
|
||||||
|
daemon_signature() {
|
||||||
|
local fw_mode port state_file log_stdout log_stderr
|
||||||
|
config_get fw_mode settings fw_mode 'nftables'
|
||||||
|
config_get port settings port '41641'
|
||||||
|
config_get state_file settings state_file '/etc/tailscale/tailscaled.state'
|
||||||
|
config_get_bool log_stdout settings log_stdout 1
|
||||||
|
config_get_bool log_stderr settings log_stderr 1
|
||||||
|
echo "$fw_mode|$port|$state_file|$log_stdout|$log_stderr"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns 0 when the tailscale daemon is managed and currently running.
|
||||||
|
tailscale_running() {
|
||||||
|
[ -x "$TAILSCALE_INIT" ] || return 1
|
||||||
|
"$TAILSCALE_INIT" running >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
# Add/remove DNS forward entry in dnsmasq for MagicDNS via OpenWrt
|
# Add/remove DNS forward entry in dnsmasq for MagicDNS via OpenWrt
|
||||||
setup_tailscale_dns_forward() {
|
setup_tailscale_dns_forward() {
|
||||||
if [ ! -f /etc/config/dhcp ] || [ ! -x /etc/init.d/dnsmasq ]; then
|
if [ ! -f /etc/config/dhcp ] || [ ! -x /etc/init.d/dnsmasq ]; then
|
||||||
@@ -49,27 +83,52 @@ remove_tailscale_dns_forward() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local entry="/.ts.net/100.100.100.100"
|
local entry="/.ts.net/100.100.100.100"
|
||||||
local idx=0
|
local found=0 s
|
||||||
local removed=0
|
for s in $(uci -q get dhcp.@dnsmasq[0].server); do
|
||||||
while true; do
|
[ "$s" = "$entry" ] && found=1
|
||||||
local val
|
|
||||||
val=$(uci -q get dhcp.@dnsmasq[0].server."${idx}" 2>/dev/null)
|
|
||||||
[ -z "$val" ] && break
|
|
||||||
if [ "$val" = "$entry" ]; then
|
|
||||||
uci -q del_list dhcp.@dnsmasq[0].server="$entry"
|
|
||||||
removed=1
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
idx=$((idx + 1))
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ "$removed" = "1" ]; then
|
if [ "$found" = "1" ]; then
|
||||||
|
uci -q del_list dhcp.@dnsmasq[0].server="$entry"
|
||||||
uci commit dhcp
|
uci commit dhcp
|
||||||
/etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null
|
/etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null
|
||||||
logger -t "$NAME" "Removed Tailscale DNS forward entry: $entry"
|
logger -t "$NAME" "Removed Tailscale DNS forward entry: $entry"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Keep the dnsmasq forward record in sync with dns_mode.
|
||||||
|
#
|
||||||
|
# Only magicdns takes resolution away from dnsmasq; both openwrt_forward and
|
||||||
|
# disabled leave dnsmasq authoritative, so the forward entry stays in place
|
||||||
|
# unless the user actually switches to magicdns. This mirrors what the old
|
||||||
|
# code did for openwrt_forward but stops deleting the entry when the mode goes
|
||||||
|
# back to disabled, which used to silently break *.ts.net resolution.
|
||||||
|
apply_dns_forward() {
|
||||||
|
local dns_mode prev=''
|
||||||
|
config_get dns_mode settings dns_mode 'disabled'
|
||||||
|
|
||||||
|
[ -f "$DNS_MODE_FILE" ] && prev=$(cat "$DNS_MODE_FILE")
|
||||||
|
|
||||||
|
if [ "$dns_mode" = "$prev" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$dns_mode" in
|
||||||
|
magicdns)
|
||||||
|
remove_tailscale_dns_forward
|
||||||
|
;;
|
||||||
|
openwrt_forward)
|
||||||
|
setup_tailscale_dns_forward
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# disabled: leave whatever the previous mode put in place,
|
||||||
|
# dnsmasq is authoritative either way
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "$dns_mode" > "$DNS_MODE_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
apply_settings() {
|
apply_settings() {
|
||||||
local ts_bin
|
local ts_bin
|
||||||
if [ -x /usr/sbin/tailscale ]; then
|
if [ -x /usr/sbin/tailscale ]; then
|
||||||
@@ -143,16 +202,10 @@ apply_settings() {
|
|||||||
case "$dns_mode" in
|
case "$dns_mode" in
|
||||||
magicdns)
|
magicdns)
|
||||||
set -- "$@" --accept-dns=true
|
set -- "$@" --accept-dns=true
|
||||||
remove_tailscale_dns_forward
|
|
||||||
;;
|
|
||||||
openwrt_forward)
|
|
||||||
set -- "$@" --accept-dns=false
|
|
||||||
setup_tailscale_dns_forward
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# disabled (default)
|
# openwrt_forward and disabled both let dnsmasq own resolution
|
||||||
set -- "$@" --accept-dns=false
|
set -- "$@" --accept-dns=false
|
||||||
remove_tailscale_dns_forward
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -186,7 +239,7 @@ apply_settings() {
|
|||||||
# --relay-server-port (requires tailscale >= 1.90.5)
|
# --relay-server-port (requires tailscale >= 1.90.5)
|
||||||
local ts_ver
|
local ts_ver
|
||||||
ts_ver=$("$ts_bin" version 2>/dev/null | head -n1)
|
ts_ver=$("$ts_bin" version 2>/dev/null | head -n1)
|
||||||
if version_gte "$ts_ver" "1.90.5"; then
|
if [ -n "$ts_ver" ] && version_gte "$ts_ver" "1.90.5"; then
|
||||||
if [ "$enable_relay" = "1" ]; then
|
if [ "$enable_relay" = "1" ]; then
|
||||||
set -- "$@" "--relay-server-port=${relay_server_port:-40000}"
|
set -- "$@" "--relay-server-port=${relay_server_port:-40000}"
|
||||||
else
|
else
|
||||||
@@ -208,26 +261,146 @@ apply_settings() {
|
|||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_service_state() {
|
# Enable or disable autostart and the running daemon.
|
||||||
config_load tailscale
|
#
|
||||||
local service_enabled
|
# The enable direction is applied here because starting a stopped daemon is
|
||||||
|
# harmless. The disable direction is only *armed* from the RPC (before the
|
||||||
|
# commit), because stopping is not reversible and the UCI rollback timer can
|
||||||
|
# still fire afterwards: rpcd commits first and arms the timer last, and a
|
||||||
|
# rollback only restores files. Keeping the actual stop in the reload handler
|
||||||
|
# means a rolled back config lands on service_enabled=1 while the daemon is
|
||||||
|
# still up, and the next apply simply drops the request.
|
||||||
|
sync_service_enabled() {
|
||||||
|
local service_enabled=1 prev=''
|
||||||
|
local requested=0
|
||||||
config_get service_enabled settings service_enabled '1'
|
config_get service_enabled settings service_enabled '1'
|
||||||
|
|
||||||
if [ "$service_enabled" = "0" ]; then
|
prev=$(cat "$ENABLED_FILE" 2>/dev/null)
|
||||||
logger -t "$NAME" "Service is disabled, stopping tailscale"
|
case "$prev" in
|
||||||
/etc/init.d/tailscale stop 2>/dev/null
|
0|1) ;;
|
||||||
/etc/init.d/tailscale disable 2>/dev/null
|
*)
|
||||||
killall tailscaled 2>/dev/null
|
# No usable record of the last applied value (first apply after
|
||||||
else
|
# an upgrade, or a truncated file). Adopt the configuration as
|
||||||
logger -t "$NAME" "Service is enabled, ensuring tailscale is running"
|
# the baseline instead of treating it as a transition: starting
|
||||||
/etc/init.d/tailscale enable 2>/dev/null
|
# a daemon here would run start_service, which arms another UCI
|
||||||
/etc/init.d/tailscale start 2>/dev/null
|
# rollback window right after the one this apply is inside.
|
||||||
apply_settings
|
echo "$service_enabled" > "$ENABLED_FILE"
|
||||||
|
prev="$service_enabled"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -f "$DISABLE_REQUEST_FILE" ]; then
|
||||||
|
requested=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$service_enabled" = "0" ]; then
|
||||||
|
if [ "$prev" = "0" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only stop when the UI asked for it, a hand edited config is
|
||||||
|
# respected for autostart but never kills a running tunnel.
|
||||||
|
if [ "$requested" -eq 0 ]; then
|
||||||
|
logger -t "$NAME" "service_enabled=0 for the first time, disabling autostart only"
|
||||||
|
"$TAILSCALE_INIT" disable 2>/dev/null
|
||||||
|
echo "0" > "$ENABLED_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
logger -t "$NAME" "Service disabled, stopping tailscale"
|
||||||
|
"$TAILSCALE_INIT" stop 2>/dev/null
|
||||||
|
"$TAILSCALE_INIT" disable 2>/dev/null
|
||||||
|
rm -f "$DISABLE_REQUEST_FILE"
|
||||||
|
echo "0" > "$ENABLED_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$DISABLE_REQUEST_FILE"
|
||||||
|
|
||||||
|
# The service should run. A daemon that is already up must not be touched:
|
||||||
|
# that is the whole point of this handler. A daemon that is down is only
|
||||||
|
# brought back when the user explicitly turned the service on in this
|
||||||
|
# apply, not because some unrelated option happened to be saved, and not
|
||||||
|
# because it crashed or was stopped by hand.
|
||||||
|
if tailscale_running; then
|
||||||
|
[ "$prev" = "1" ] || echo "1" > "$ENABLED_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$prev" = "1" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
logger -t "$NAME" "Service enabled, starting tailscale"
|
||||||
|
"$TAILSCALE_INIT" enable 2>/dev/null
|
||||||
|
tailscale_running || "$TAILSCALE_INIT" start 2>/dev/null
|
||||||
|
echo "1" > "$ENABLED_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait briefly for procd to bring the daemon back after a restart. Called from
|
||||||
|
# reload_service, which rc.common runs outside of any function, so the loop
|
||||||
|
# counter has to live in a function of its own to stay out of the global scope.
|
||||||
|
wait_for_daemon() {
|
||||||
|
local waited=0
|
||||||
|
while [ "$waited" -lt 2 ]; do
|
||||||
|
tailscale_running && return 0
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_service_state() {
|
||||||
|
if [ ! -x "$TAILSCALE_INIT" ]; then
|
||||||
|
logger -t "$NAME" "tailscale init script not found, keeping settings only"
|
||||||
|
apply_settings
|
||||||
|
apply_dns_forward
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sync_service_enabled
|
||||||
|
|
||||||
|
# Nothing may touch a running daemon here. The upstream init runs
|
||||||
|
# 'tailscaled --cleanup' before handing the instance to procd, which tears
|
||||||
|
# down tailscale0 addresses, table 52 routes and the netfilter rules while
|
||||||
|
# the old process is still alive. Running 'start' on a live service (or
|
||||||
|
# 'restart' without reason) therefore leaves a node that still answers
|
||||||
|
# 'tailscale status' and 'tailscale ping' but has no working kernel state.
|
||||||
|
if ! tailscale_running; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local desired
|
||||||
|
desired=$(daemon_signature)
|
||||||
|
|
||||||
|
if [ -f "$DAEMON_SIG_FILE" ] && [ "$(cat "$DAEMON_SIG_FILE")" != "$desired" ]; then
|
||||||
|
logger -t "$NAME" "Daemon options changed, restarting tailscale"
|
||||||
|
"$TAILSCALE_INIT" restart 2>/dev/null
|
||||||
|
wait_for_daemon || logger -t "$NAME" "tailscale did not come back within 2s"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$desired" > "$DAEMON_SIG_FILE"
|
||||||
|
|
||||||
|
apply_settings
|
||||||
|
apply_dns_forward
|
||||||
}
|
}
|
||||||
|
|
||||||
start_service() {
|
start_service() {
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
config_load tailscale
|
||||||
|
daemon_signature > "$DAEMON_SIG_FILE"
|
||||||
|
# Record the desired flag without acting on it: at boot the service state
|
||||||
|
# is already correct and acting here would start or stop a daemon we do
|
||||||
|
# not own. Only seed the file when there is nothing to inherit, so a
|
||||||
|
# manual `disable` in the shell survives until the reload handler sees a
|
||||||
|
# real transition.
|
||||||
|
if [ ! -f "$ENABLED_FILE" ]; then
|
||||||
|
local service_enabled=1
|
||||||
|
config_get service_enabled settings service_enabled '1'
|
||||||
|
echo "$service_enabled" > "$ENABLED_FILE"
|
||||||
|
fi
|
||||||
apply_settings
|
apply_settings
|
||||||
|
apply_dns_forward
|
||||||
}
|
}
|
||||||
|
|
||||||
service_triggers() {
|
service_triggers() {
|
||||||
@@ -235,5 +408,6 @@ service_triggers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reload_service() {
|
reload_service() {
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
handle_service_state
|
handle_service_state
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Background worker for the "Auto Configure Firewall" action.
|
||||||
|
#
|
||||||
|
# Kept outside the rpcd plugin on purpose: the RPC must return before the
|
||||||
|
# network is reloaded, otherwise a request coming through the network being
|
||||||
|
# reloaded (reverse proxy, or LuCI over Tailscale itself) dies with an XHR
|
||||||
|
# abort and the user cannot tell whether anything happened.
|
||||||
|
#
|
||||||
|
# The status file is line based rather than JSON so that a message containing
|
||||||
|
# a backslash, a quote or a tab cannot corrupt it: whoever reads it builds the
|
||||||
|
# JSON itself. Line 4 is the run id handed over by the RPC, so a result from a
|
||||||
|
# previous run can never be mistaken for this one.
|
||||||
|
#
|
||||||
|
# Usage: tailscale-setup-firewall <run-id>
|
||||||
|
|
||||||
|
STATE_FILE=/var/run/tailscale-fwsetup.status
|
||||||
|
LOCK_FILE=/var/run/tailscale-fwsetup.lock
|
||||||
|
|
||||||
|
RUN_ID="$1"
|
||||||
|
|
||||||
|
write_state() {
|
||||||
|
{
|
||||||
|
printf '%s\n' "$1"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
# one line only: the reader takes fixed line numbers, so a message that
|
||||||
|
# wrapped would shift the run id off the last line. tr cannot strip a
|
||||||
|
# real newline, sed can.
|
||||||
|
if [ -n "$3" ]; then
|
||||||
|
printf '%s' "$3" | sed -e 's/\r//g' -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g'
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
printf '%s\n' "$RUN_ID"
|
||||||
|
} > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only one worker at a time: a second click while a reload is running would
|
||||||
|
# queue another network reload on top of an unfinished one. mkdir is atomic and
|
||||||
|
# is the only portable primitive available here; a test-then-write pair lets
|
||||||
|
# two workers through at the same moment. Stale locks are recognised by age so
|
||||||
|
# a recycled pid or a killed worker cannot block forever.
|
||||||
|
LOCK_DIR="$LOCK_FILE.d"
|
||||||
|
lock_age() {
|
||||||
|
local ts now
|
||||||
|
ts=$(cat "$LOCK_DIR/ts" 2>/dev/null)
|
||||||
|
case "$ts" in ''|*[!0-9]*) echo 999999; return ;; esac
|
||||||
|
now=$(date +%s)
|
||||||
|
echo $((now - ts))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
||||||
|
if [ "$(lock_age)" -gt 300 ]; then
|
||||||
|
# the holder is gone; take the lock over
|
||||||
|
rm -rf "$LOCK_DIR"
|
||||||
|
mkdir "$LOCK_DIR" 2>/dev/null || exit 0
|
||||||
|
else
|
||||||
|
# someone else is already reloading: say so instead of leaving the
|
||||||
|
# status file at "queued" and letting the UI time out
|
||||||
|
write_state busy locked "Another firewall reload is already running"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "$$" > "$LOCK_DIR/pid"
|
||||||
|
date +%s > "$LOCK_DIR/ts"
|
||||||
|
|
||||||
|
trap 'write_state failed aborted "Worker terminated unexpectedly"; rm -rf "$LOCK_DIR"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
write_state running network "Reloading network"
|
||||||
|
|
||||||
|
if ! /etc/init.d/network reload >/dev/null 2>&1; then
|
||||||
|
write_state failed network "Network reload failed"
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
rm -rf "$LOCK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_state running firewall "Reloading firewall"
|
||||||
|
|
||||||
|
if ! /etc/init.d/firewall reload >/dev/null 2>&1; then
|
||||||
|
write_state failed firewall "Firewall reload failed"
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
rm -rf "$LOCK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_state done done "Firewall configuration applied"
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
rm -rf "$LOCK_DIR"
|
||||||
+4
-2
@@ -7,7 +7,8 @@
|
|||||||
"get_status",
|
"get_status",
|
||||||
"get_settings",
|
"get_settings",
|
||||||
"get_subroutes",
|
"get_subroutes",
|
||||||
"get_logs"
|
"get_logs",
|
||||||
|
"get_firewall_status"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"uci": [ "tailscale" ]
|
"uci": [ "tailscale" ]
|
||||||
@@ -17,7 +18,8 @@
|
|||||||
"tailscale": [
|
"tailscale": [
|
||||||
"do_login",
|
"do_login",
|
||||||
"do_logout",
|
"do_logout",
|
||||||
"setup_firewall"
|
"setup_firewall",
|
||||||
|
"set_service_enabled"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"uci": [ "tailscale" ]
|
"uci": [ "tailscale" ]
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ methods.do_login = {
|
|||||||
if (status.status != 'logout') {
|
if (status.status != 'logout') {
|
||||||
return { error: 'Tailscale is already logged in and running.' };
|
return { error: 'Tailscale is already logged in and running.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 1. Prepare and Run Login Command (Once) ---
|
// --- 1. Prepare and Run Login Command (Once) ---
|
||||||
const loginserver = trim(form_data.loginserver) || '';
|
const loginserver = trim(form_data.loginserver) || '';
|
||||||
const loginserver_authkey = trim(form_data.loginserver_authkey) || '';
|
const loginserver_authkey = trim(form_data.loginserver_authkey) || '';
|
||||||
@@ -177,7 +176,7 @@ methods.do_login = {
|
|||||||
let parts = split(trline, ' ');
|
let parts = split(trline, ' ');
|
||||||
for (let part in parts) {
|
for (let part in parts) {
|
||||||
if (index(part, 'http') != -1) {
|
if (index(part, 'http') != -1) {
|
||||||
return { url: part };
|
return { success: true, url: part };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +198,7 @@ methods.do_logout = {
|
|||||||
if (logout_result.code != 0) {
|
if (logout_result.code != 0) {
|
||||||
return { error: 'Failed to logout: ' + logout_result.stderr };
|
return { error: 'Failed to logout: ' + logout_result.stderr };
|
||||||
}
|
}
|
||||||
return { success: true };
|
return { success: true, message: 'Logged out.' };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,6 +240,7 @@ methods.setup_firewall = {
|
|||||||
|
|
||||||
let changed_network = false;
|
let changed_network = false;
|
||||||
let changed_firewall = false;
|
let changed_firewall = false;
|
||||||
|
let run_id = 0;
|
||||||
|
|
||||||
// 1. config Network Interface
|
// 1. config Network Interface
|
||||||
let net_ts = uci.get('network', 'tailscale');
|
let net_ts = uci.get('network', 'tailscale');
|
||||||
@@ -335,24 +335,57 @@ methods.setup_firewall = {
|
|||||||
changed_firewall = true;
|
changed_firewall = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. save
|
// 4. save and hand the reload to a background worker
|
||||||
|
//
|
||||||
|
// Reloading network/firewall synchronously here was the reason the
|
||||||
|
// button failed through a reverse proxy: the reload tears down the
|
||||||
|
// connection carrying this very request. The worker records its
|
||||||
|
// progress in /var/run/tailscale-fwsetup.status and the UI polls it.
|
||||||
if (changed_network) {
|
if (changed_network) {
|
||||||
uci.save('network');
|
uci.save('network');
|
||||||
uci.commit('network');
|
uci.commit('network');
|
||||||
exec('/etc/init.d/network reload');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed_firewall) {
|
if (changed_firewall) {
|
||||||
uci.save('firewall');
|
uci.save('firewall');
|
||||||
uci.commit('firewall');
|
uci.commit('firewall');
|
||||||
exec('/etc/init.d/firewall reload');
|
}
|
||||||
|
|
||||||
|
if (changed_network || changed_firewall) {
|
||||||
|
if (!access('/usr/libexec/tailscale-setup-firewall')) {
|
||||||
|
return { error: 'Firewall worker /usr/libexec/tailscale-setup-firewall is missing. Reinstall the package.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// The run id ties the polled result to this request, and the
|
||||||
|
// queued state replaces any previous result before the worker
|
||||||
|
// gets a chance to overwrite it. Without both, a poll arriving
|
||||||
|
// while the worker is still starting would read the previous
|
||||||
|
// run's "done" and report success for work that has not run.
|
||||||
|
run_id = time();
|
||||||
|
let st = writefile('/var/run/tailscale-fwsetup.status.tmp',
|
||||||
|
'queued\nstart\n\n' + run_id + '\n');
|
||||||
|
if (st == null || rename('/var/run/tailscale-fwsetup.status.tmp', '/var/run/tailscale-fwsetup.status') == null) {
|
||||||
|
// Without a writable status file the poll can never see a
|
||||||
|
// result, so say so instead of letting the UI wait it out.
|
||||||
|
return { error: 'Cannot write the firewall status file in /var/run. Configuration was not changed.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// The background shell exits immediately, so its status only
|
||||||
|
// tells us whether sh itself started. Anything the worker
|
||||||
|
// reports afterwards lands in the status file.
|
||||||
|
let spawn = exec('/bin/sh -c ' + shell_quote('/usr/libexec/tailscale-setup-firewall ' + run_id + ' >/dev/null 2>&1 &'));
|
||||||
|
if (spawn.code != 0) {
|
||||||
|
return { error: 'Failed to start the firewall worker: ' + join('\n', spawn.stdout) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
changed_network: changed_network,
|
changed_network: changed_network,
|
||||||
changed_firewall: changed_firewall,
|
changed_firewall: changed_firewall,
|
||||||
message: (changed_network || changed_firewall) ? 'Tailscale firewall/interface configuration applied.' : 'Tailscale firewall/interface already configured.'
|
job: (changed_network || changed_firewall) ? 'running' : 'idle',
|
||||||
|
run_id: run_id,
|
||||||
|
message: (changed_network || changed_firewall) ? 'Configuration committed, reloading network and firewall in the background.' : 'Tailscale firewall/interface already configured.'
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -361,6 +394,75 @@ methods.setup_firewall = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Record that the user asked to disable the service.
|
||||||
|
//
|
||||||
|
// The actual stop happens in /etc/init.d/tailscale-settings on the reload
|
||||||
|
// trigger, which runs right after rpcd commits and *before* it arms the LuCI
|
||||||
|
// rollback timer. Stopping here would not survive a rollback: rpcd restores
|
||||||
|
// the config files but never restarts a daemon it did not stop, so the user
|
||||||
|
// would end up with service_enabled=1 and a dead tunnel. The request file is
|
||||||
|
// dropped by the reload handler as soon as the config says enabled again.
|
||||||
|
methods.set_service_enabled = {
|
||||||
|
args: { enabled: '' },
|
||||||
|
call: function(request) {
|
||||||
|
let enabled = request?.args?.enabled;
|
||||||
|
if (enabled != '0' && enabled != '1') {
|
||||||
|
return { error: 'Invalid enabled value, expected 0 or 1.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled == '1') {
|
||||||
|
let r = exec('rm -f /var/run/tailscale-settings/disable-requested');
|
||||||
|
if (r.code != 0) {
|
||||||
|
return { error: 'Failed to clear the disable request: ' + join('\n', r.stdout) };
|
||||||
|
}
|
||||||
|
return { success: true, armed: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// No daemon probe here: get_status shells out to 'tailscale status'
|
||||||
|
// and would delay the apply for nothing. A request armed on a box
|
||||||
|
// without the tailscale package is simply ignored by the reload
|
||||||
|
// handler, which bails out when the init script is missing.
|
||||||
|
let r = exec('mkdir -p /var/run/tailscale-settings && touch /var/run/tailscale-settings/disable-requested');
|
||||||
|
if (r.code != 0) {
|
||||||
|
return { error: 'Failed to arm the disable request: ' + join('\n', r.stdout) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, armed: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Result of the last background firewall setup job.
|
||||||
|
//
|
||||||
|
// Read here rather than in the browser so the ACL needs no file rights: the
|
||||||
|
// ucode plugin runs as root and rpcd only enforces path ACLs on the file
|
||||||
|
// ubus object.
|
||||||
|
methods.get_firewall_status = {
|
||||||
|
call: function() {
|
||||||
|
let path = '/var/run/tailscale-fwsetup.status';
|
||||||
|
if (!access(path)) {
|
||||||
|
return { success: true, state: 'idle', step: '', message: '', run_id: 0 };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let raw = readfile(path);
|
||||||
|
if (raw == null) {
|
||||||
|
return { success: true, state: 'idle', step: '', message: '', run_id: 0 };
|
||||||
|
}
|
||||||
|
let lines = split(rtrim(raw, '\n'), '\n');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
state: lines[0] || 'unknown',
|
||||||
|
step: lines[1] || '',
|
||||||
|
message: lines[2] || '',
|
||||||
|
// the run id is always the last line, so a message that somehow
|
||||||
|
// still contains a newline cannot shift it into line 3
|
||||||
|
run_id: int(lines[length(lines) - 1]) || 0
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return { success: true, state: 'unknown', step: '', message: 'Unreadable job state: ' + e, run_id: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
methods.get_logs = {
|
methods.get_logs = {
|
||||||
args: { lines: 200 },
|
args: { lines: 200 },
|
||||||
call: function(request) {
|
call: function(request) {
|
||||||
@@ -368,9 +470,9 @@ methods.get_logs = {
|
|||||||
let cmd = 'logread -l ' + lines + ' 2>/dev/null | grep -i -E "tailscale" || true';
|
let cmd = 'logread -l ' + lines + ' 2>/dev/null | grep -i -E "tailscale" || true';
|
||||||
let result = exec(cmd);
|
let result = exec(cmd);
|
||||||
if (result.code == 0) {
|
if (result.code == 0) {
|
||||||
return { logs: result.stdout };
|
return { success: true, logs: result.stdout };
|
||||||
}
|
}
|
||||||
return { logs: [], error: join(' ', result.stderr) };
|
return { success: false, logs: [], error: join(' ', result.stderr) };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=mptcp
|
PKG_NAME:=mptcp
|
||||||
PKG_VERSION:=6.1
|
PKG_VERSION:=6.1
|
||||||
PKG_RELEASE:=28
|
PKG_RELEASE:=29
|
||||||
|
|
||||||
PKG_MAINTAINER:=Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
|
PKG_MAINTAINER:=Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
|
||||||
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
|
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
|
||||||
|
|||||||
@@ -265,28 +265,43 @@ interface_macaddr_count() {
|
|||||||
[ "$macaddr" = "$dmacaddr" ] && [ "${intf%.*}" = "$intf" ] && nbmac=$((nbmac+1))
|
[ "$macaddr" = "$dmacaddr" ] && [ "${intf%.*}" = "$intf" ] && nbmac=$((nbmac+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# procd reloads this init on every interface event. A "uci commit" writes
|
||||||
|
# the whole pending delta of the config, other processes' staged changes
|
||||||
|
# included (openmptcprouter-vps's uncommitted token went to flash on each
|
||||||
|
# reload), so an option is only set when it differs, and a config is only
|
||||||
|
# committed when this run changed it.
|
||||||
|
_mptcp_uci_set() {
|
||||||
|
[ "$(uci -q get "$1")" = "$2" ] && return 0
|
||||||
|
uci -q set "$1=$2"
|
||||||
|
_mptcp_uci_changed="$_mptcp_uci_changed ${1%%.*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
_mptcp_uci_delete() {
|
||||||
|
uci -q get "$1" >/dev/null || return 0
|
||||||
|
uci -q delete "$1"
|
||||||
|
_mptcp_uci_changed="$_mptcp_uci_changed ${1%%.*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
_mptcp_uci_commit() {
|
||||||
|
case " $_mptcp_uci_changed " in
|
||||||
|
*" $1 "*) uci -q commit "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
interface_max_metric() {
|
interface_max_metric() {
|
||||||
local config="$1"
|
local config="$1"
|
||||||
case "$1" in
|
case "$1" in
|
||||||
omrvpn)
|
omrvpn)
|
||||||
uci -q batch <<-EOF >/dev/null
|
_mptcp_uci_set network.${config}.metric 1500
|
||||||
set network.${config}.metric=1500
|
_mptcp_uci_set network.${config}.ip4table 1500
|
||||||
set network.${config}.ip4table=1500
|
_mptcp_uci_set network.${config}.ip6table 61500
|
||||||
set network.${config}.ip6table=61500
|
_mptcp_uci_set openmptcprouter.${config}.metric 1500
|
||||||
commit network
|
|
||||||
set openmptcprouter.${config}.metric=1500
|
|
||||||
commit openmptcprouter
|
|
||||||
EOF
|
|
||||||
;;
|
;;
|
||||||
omr6in4)
|
omr6in4)
|
||||||
uci -q batch <<-EOF >/dev/null
|
_mptcp_uci_set network.${config}.metric 1201
|
||||||
set network.${config}.metric=1201
|
_mptcp_uci_set network.${config}.ip4table 1201
|
||||||
set network.${config}.ip4table=1201
|
_mptcp_uci_set network.${config}.ip6table 61201
|
||||||
set network.${config}.ip6table=61201
|
_mptcp_uci_set openmptcprouter.${config}.metric 1201
|
||||||
commit network
|
|
||||||
set openmptcprouter.${config}.metric=1201
|
|
||||||
commit openmptcprouter
|
|
||||||
EOF
|
|
||||||
;;
|
;;
|
||||||
lan)
|
lan)
|
||||||
# only when needed: this runs on every reload, sed -i writes flash
|
# only when needed: this runs on every reload, sed -i writes flash
|
||||||
@@ -477,10 +492,10 @@ interface_multipath_settings() {
|
|||||||
# metric="9998"
|
# metric="9998"
|
||||||
fi
|
fi
|
||||||
config_set "$config" metric $metric
|
config_set "$config" metric $metric
|
||||||
uci -q set network.${config}.metric=$metric
|
_mptcp_uci_set network.${config}.metric "$metric"
|
||||||
uci -q set network.${config}.ip4table=$metric
|
_mptcp_uci_set network.${config}.ip4table "$metric"
|
||||||
uci -q set network.${config}.ip6table=6$metric
|
_mptcp_uci_set network.${config}.ip6table "6$metric"
|
||||||
uci -q set openmptcprouter.${config}.metric=$metric
|
_mptcp_uci_set openmptcprouter.${config}.metric "$metric"
|
||||||
config_get mode "$config" multipath
|
config_get mode "$config" multipath
|
||||||
config_get disabled "$config" disabled
|
config_get disabled "$config" disabled
|
||||||
[ "$disabled" = "1" ] && mode="off"
|
[ "$disabled" = "1" ] && mode="off"
|
||||||
@@ -504,7 +519,7 @@ interface_multipath_settings() {
|
|||||||
mode="$(uci -q get openmptcprouter.${config}.multipath)"
|
mode="$(uci -q get openmptcprouter.${config}.multipath)"
|
||||||
[ -n "$mode" ] && {
|
[ -n "$mode" ] && {
|
||||||
logger -t "MPTCP" "No multipath setting for $config, restoring $mode from openmptcprouter config"
|
logger -t "MPTCP" "No multipath setting for $config, restoring $mode from openmptcprouter config"
|
||||||
uci -q set network.${config}.multipath="$mode"
|
_mptcp_uci_set network.${config}.multipath "$mode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[ "$mode" = "" ] && {
|
[ "$mode" = "" ] && {
|
||||||
@@ -513,8 +528,8 @@ interface_multipath_settings() {
|
|||||||
[ "$config" = "omr6in4" ] && mode="off"
|
[ "$config" = "omr6in4" ] && mode="off"
|
||||||
[ "$mode" = "" ] && mode="off"
|
[ "$mode" = "" ] && mode="off"
|
||||||
logger -t "MPTCP" "Multipath not defined for $config set to $mode"
|
logger -t "MPTCP" "Multipath not defined for $config set to $mode"
|
||||||
uci -q set network.${config}.multipath="$mode"
|
_mptcp_uci_set network.${config}.multipath "$mode"
|
||||||
uci -q set openmptcprouter.${config}.multipath="$mode"
|
_mptcp_uci_set openmptcprouter.${config}.multipath "$mode"
|
||||||
}
|
}
|
||||||
[ "$mode" != "off" ] && {
|
[ "$mode" != "off" ] && {
|
||||||
# LRO, TSO and UFO break MPTCP packets scheduling, always disable them on multipath interfaces
|
# LRO, TSO and UFO break MPTCP packets scheduling, always disable them on multipath interfaces
|
||||||
@@ -526,9 +541,9 @@ interface_multipath_settings() {
|
|||||||
[ -n "$mptcpintf" ] && mptcpintf="$mptcpintf $iface"
|
[ -n "$mptcpintf" ] && mptcpintf="$mptcpintf $iface"
|
||||||
[ -z "$mptcpintf" ] && mptcpintf="$iface"
|
[ -z "$mptcpintf" ] && mptcpintf="$iface"
|
||||||
[ -z "$mptcpmintf" ] && mptcpmintf="$config"
|
[ -z "$mptcpmintf" ] && mptcpmintf="$config"
|
||||||
uci -q set network.${config}.defaultroute=0
|
_mptcp_uci_set network.${config}.defaultroute 0
|
||||||
uci -q set network.${config}.peerdns=0
|
_mptcp_uci_set network.${config}.peerdns 0
|
||||||
uci -q delete network.${config}.dns
|
_mptcp_uci_delete network.${config}.dns
|
||||||
echo '' > /etc/resolv.conf >/dev/null 2>&1
|
echo '' > /etc/resolv.conf >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
[ "$mode" = "master" ] && {
|
[ "$mode" = "master" ] && {
|
||||||
@@ -537,14 +552,14 @@ interface_multipath_settings() {
|
|||||||
logger -t "MPTCP" "Multipath master already set, disable master for $config"
|
logger -t "MPTCP" "Multipath master already set, disable master for $config"
|
||||||
mode="on"
|
mode="on"
|
||||||
config_set "$config" multipath "on"
|
config_set "$config" multipath "on"
|
||||||
uci -q set network.${config}.multipath="on"
|
_mptcp_uci_set network.${config}.multipath "on"
|
||||||
uci -q set openmptcprouter.${config}.multipath="on"
|
_mptcp_uci_set openmptcprouter.${config}.multipath "on"
|
||||||
else
|
else
|
||||||
master="$config"
|
master="$config"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
uci -q set openmptcprouter.${config}="interface"
|
_mptcp_uci_set openmptcprouter.${config} "interface"
|
||||||
uci -q set openmptcprouter.${config}.multipath="$mode"
|
_mptcp_uci_set openmptcprouter.${config}.multipath "$mode"
|
||||||
|
|
||||||
config_get macaddr "$config" macaddr ""
|
config_get macaddr "$config" macaddr ""
|
||||||
[ -n "$macaddr" ] && {
|
[ -n "$macaddr" ] && {
|
||||||
@@ -552,7 +567,7 @@ interface_multipath_settings() {
|
|||||||
config_foreach interface_macaddr_count interface $macaddr
|
config_foreach interface_macaddr_count interface $macaddr
|
||||||
[ "$nbmac" != "1" ] && {
|
[ "$nbmac" != "1" ] && {
|
||||||
logger -t "MPTCP" "Duplicate MAC saved, removing"
|
logger -t "MPTCP" "Duplicate MAC saved, removing"
|
||||||
uci -q delete network.${config}.macaddr
|
_mptcp_uci_delete network.${config}.macaddr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,6 +604,13 @@ interface_multipath_settings() {
|
|||||||
config_get ipaddr $config ipaddr
|
config_get ipaddr $config ipaddr
|
||||||
config_get gateway $config gateway
|
config_get gateway $config gateway
|
||||||
config_get netmask $config netmask
|
config_get netmask $config netmask
|
||||||
|
# ipaddr can be a list in CIDR notation, with no netmask: network
|
||||||
|
# stayed empty and the WAN got no table route, rule or qdisc
|
||||||
|
ipaddr="${ipaddr%% *}"
|
||||||
|
case "$ipaddr" in */*)
|
||||||
|
[ -z "$netmask" ] && netmask="${ipaddr#*/}"
|
||||||
|
ipaddr="${ipaddr%%/*}"
|
||||||
|
;; esac
|
||||||
[ -n "$ipaddr" ] && [ -n "$netmask" ] && netmask=`ipcalc.sh $ipaddr/$netmask | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'`
|
[ -n "$ipaddr" ] && [ -n "$netmask" ] && netmask=`ipcalc.sh $ipaddr/$netmask | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'`
|
||||||
[ -n "$ipaddr" ] && [ -n "$netmask" ] && network=`ipcalc.sh $ipaddr/$netmask | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
|
[ -n "$ipaddr" ] && [ -n "$netmask" ] && network=`ipcalc.sh $ipaddr/$netmask | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
|
||||||
elif [ "$proto" != "gre" ]; then
|
elif [ "$proto" != "gre" ]; then
|
||||||
@@ -970,6 +992,7 @@ start_service() {
|
|||||||
mptcpmintf=""
|
mptcpmintf=""
|
||||||
master=""
|
master=""
|
||||||
count=0
|
count=0
|
||||||
|
_mptcp_uci_changed=""
|
||||||
config_load openmptcprouter
|
config_load openmptcprouter
|
||||||
config_foreach interface_max_metric interface
|
config_foreach interface_max_metric interface
|
||||||
|
|
||||||
@@ -1000,13 +1023,13 @@ start_service() {
|
|||||||
intfmaster="$mptcpmintf"
|
intfmaster="$mptcpmintf"
|
||||||
[ "$intfmaster" != "" ] && mptcp_has_master_multipath && {
|
[ "$intfmaster" != "" ] && mptcp_has_master_multipath && {
|
||||||
logger -t "MPTCP" "Master multipath interface disabled, setting it to $intfmaster"
|
logger -t "MPTCP" "Master multipath interface disabled, setting it to $intfmaster"
|
||||||
uci -q set network.${intfmaster}.multipath="master"
|
_mptcp_uci_set network.${intfmaster}.multipath "master"
|
||||||
uci -q set openmptcprouter.${intfmaster}.multipath="master"
|
_mptcp_uci_set openmptcprouter.${intfmaster}.multipath "master"
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
[ -n "$(uci -q changes network)" ] && uci -q commit network
|
_mptcp_uci_commit network
|
||||||
[ -n "$(uci -q changes openmptcprouter)" ] && uci -q commit openmptcprouter
|
_mptcp_uci_commit openmptcprouter
|
||||||
case "$board_name" in
|
case "$board_name" in
|
||||||
*raspberry*) case "$board_name" in *4-model-b*|*5*) ;; *)
|
*raspberry*) case "$board_name" in *4-model-b*|*5*) ;; *)
|
||||||
ethtool --offload eth0 rx off tx off > /dev/null 2>&1
|
ethtool --offload eth0 rx off tx off > /dev/null 2>&1
|
||||||
|
|||||||
@@ -83,33 +83,27 @@ if [ "$(uci -q get network.globals.congestion)" = "bbr2" ] && [ -z "$(uname -a |
|
|||||||
commit network
|
commit network
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
if [ "$(uci -q get network.globals.mptcp_syn_retries)" = "1" ]; then
|
# The old defaults (syn_retries 1, add_addr_accepted 1) are moved once:
|
||||||
|
# this script runs again on every sysupgrade, and a 1 the user chose
|
||||||
|
# afterwards was reset each time, as were the checksum and the debug level.
|
||||||
|
# mptcp_defaults_version records which of these migrations already ran.
|
||||||
|
if [ "$(uci -q get network.globals.mptcp_defaults_version)" != "1" ]; then
|
||||||
|
if [ "$(uci -q get network.globals.mptcp_syn_retries)" = "1" ]; then
|
||||||
|
uci -q set network.globals.mptcp_syn_retries=4
|
||||||
|
fi
|
||||||
|
if [ "$(uci -q get network.globals.mptcp_add_addr_accepted)" = "1" ]; then
|
||||||
|
uci -q set network.globals.mptcp_add_addr_accepted=8
|
||||||
|
fi
|
||||||
uci -q batch <<-EOF >/dev/null
|
uci -q batch <<-EOF >/dev/null
|
||||||
set network.globals.mptcp_syn_retries=4
|
set network.globals.mptcp_defaults_version=1
|
||||||
commit network
|
commit network
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
if [ "$(uci -q get network.globals.mptcp_checksum)" != "0" ]; then
|
# enable/disable were written by an older release: the sysctl takes 1/0
|
||||||
uci -q batch <<-EOF >/dev/null
|
case "$(uci -q get network.globals.mptcp_checksum)" in
|
||||||
set network.globals.mptcp_checksum=0
|
enable) uci -q set network.globals.mptcp_checksum=1; uci -q commit network ;;
|
||||||
commit network
|
disable) uci -q set network.globals.mptcp_checksum=0; uci -q commit network ;;
|
||||||
EOF
|
esac
|
||||||
fi
|
|
||||||
if [ "$(uci -q get network.globals.mptcp_checksum)" = "enable" ]; then
|
|
||||||
uci -q batch <<-EOF >/dev/null
|
|
||||||
set network.globals.mptcp_checksum=1
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
if [ "$(uci -q get network.globals.mptcp_checksum)" = "disable" ]; then
|
|
||||||
uci -q batch <<-EOF >/dev/null
|
|
||||||
set network.globals.mptcp_checksum=0
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
if [ "$(uci -q get network.globals.mptcp_debug)" != "0" ]; then
|
|
||||||
uci -q batch <<-EOF >/dev/null
|
|
||||||
set network.globals.mptcp_debug=0
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$(uci -q show network.globals | grep mptcp_fullmesh)" = "" ]; then
|
if [ "$(uci -q show network.globals | grep mptcp_fullmesh)" = "" ]; then
|
||||||
uci -q batch <<-EOF >/dev/null
|
uci -q batch <<-EOF >/dev/null
|
||||||
@@ -127,12 +121,33 @@ if [ "$(uci -q get network.globals.mptcp_subflows)" = "" ]; then
|
|||||||
commit network
|
commit network
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
if [ "$(uci -q get network.globals.mptcp_add_addr_accepted)" = "1" ]; then
|
# A multipath_weight out of 1-256 (0, 300, set by hand or by an older
|
||||||
uci -q batch <<-EOF >/dev/null
|
# release) failed the datatype of the MPTCP pages and blocked every save
|
||||||
set network.globals.mptcp_add_addr_accepted=8
|
# there. Stored as 040-multipath-weight reads it: not a number is unset
|
||||||
commit network
|
# (100), leading zeros dropped, 0 is 1, over 256 is 256.
|
||||||
EOF
|
_weight_changed=""
|
||||||
fi
|
for _sec in $(uci -q show network | sed -n "s/^network\.\([^.]*\)\.multipath_weight=.*/\1/p"); do
|
||||||
|
_weight="$(uci -q get "network.${_sec}.multipath_weight")"
|
||||||
|
case "$_weight" in
|
||||||
|
''|*[!0-9]*) _new_weight="" ;;
|
||||||
|
*)
|
||||||
|
_new_weight="${_weight#"${_weight%%[!0]*}"}"
|
||||||
|
if [ -z "$_new_weight" ]; then
|
||||||
|
_new_weight=1
|
||||||
|
elif [ "${#_new_weight}" -gt 3 ] || [ "$_new_weight" -gt 256 ]; then
|
||||||
|
_new_weight=256
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
[ "$_new_weight" = "$_weight" ] && continue
|
||||||
|
if [ -z "$_new_weight" ]; then
|
||||||
|
uci -q delete "network.${_sec}.multipath_weight"
|
||||||
|
else
|
||||||
|
uci -q set "network.${_sec}.multipath_weight=${_new_weight}"
|
||||||
|
fi
|
||||||
|
_weight_changed=1
|
||||||
|
done
|
||||||
|
[ -n "$_weight_changed" ] && uci -q commit network
|
||||||
|
|
||||||
# The pre-procd config-change registration was dropped from here: OMR images
|
# The pre-procd config-change registration was dropped from here: OMR images
|
||||||
# ship no /etc/config/ucitrack, so it failed silently anyway, and the init
|
# ship no /etc/config/ucitrack, so it failed silently anyway, and the init
|
||||||
|
|||||||
+2
-2
@@ -7,9 +7,9 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=openlist2
|
PKG_NAME:=openlist2
|
||||||
PKG_VERSION:=4.2.7
|
PKG_VERSION:=4.2.8
|
||||||
PKG_WEB_VERSION:=4.2.6
|
PKG_WEB_VERSION:=4.2.6
|
||||||
PKG_RELEASE:=20
|
PKG_RELEASE:=21
|
||||||
|
|
||||||
PKG_SOURCE:=openlist-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=openlist-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/OpenListTeam/OpenList/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/OpenListTeam/OpenList/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
+2
-2
@@ -5,8 +5,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=sing-box
|
PKG_NAME:=sing-box
|
||||||
PKG_VERSION:=1.14.3
|
PKG_VERSION:=1.14.4
|
||||||
PKG_RELEASE:=36
|
PKG_RELEASE:=37
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
+30
-3
@@ -50,12 +50,12 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=wwand
|
PKG_NAME:=wwand
|
||||||
PKG_VERSION:=1.8.0_pre6
|
PKG_VERSION:=1.8.0_pre14
|
||||||
PKG_RELEASE:=81
|
PKG_RELEASE:=82
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||||
PKG_SOURCE_VERSION:=7988768d58da5f0f86100b1a2d63db25bdfe87c9
|
PKG_SOURCE_VERSION:=7c6f90d468a76060da225e0a2a60101b74d83bc5
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-2.0-only
|
PKG_LICENSE:=GPL-2.0-only
|
||||||
@@ -137,6 +137,32 @@ config WWAND_UCODE_SOURCE
|
|||||||
cmake capability probe falls back to shipping source on its own.
|
cmake capability probe falls back to shipping source on its own.
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
# The ucode this build links and compiles against, as a version floor for the
|
||||||
|
# device. wwand_io.so links libucode.so.<abi> and the tree may be bytecode, so
|
||||||
|
# a wwand built against a newer ucode cannot run on an older one — and with
|
||||||
|
# a plain +ucode apk installed it there anyway: a feed build from after
|
||||||
|
# OpenWrt master moved libucode 20230711 -> 20261009 (2026-10-19) went onto a
|
||||||
|
# snapshot from before it and died on "Error loading shared library
|
||||||
|
# libucode.so.20261009" (ddimension/wwand#67). `ucode (>=<date>)` makes apk
|
||||||
|
# refuse instead. apk sorts the package's own "2026.10.18~d2e8b8d2-r1" above
|
||||||
|
# "2026.10.18" (apk 3.0.5 `version --test`), so the build's own ucode passes.
|
||||||
|
#
|
||||||
|
# Read from ucode's Makefile because no build variable carries another
|
||||||
|
# package's version: package/utils/ucode in a buildroot, the base feed in an
|
||||||
|
# SDK (`feeds update` puts it under feeds/base and links package/feeds/base).
|
||||||
|
# Not found (an unusual tree) means no floor, as before — never a guess.
|
||||||
|
#
|
||||||
|
# Deliberately NOT +libucode by name, though that would record the
|
||||||
|
# ABI-versioned libucode<abi>: on a later ucode upgrade it keeps the old
|
||||||
|
# library installed, and wwand_io.so then loads it into an interpreter running
|
||||||
|
# the new one — two libucode ABIs in one process. Without it the library goes
|
||||||
|
# and files/wwand.init says why the daemon cannot start.
|
||||||
|
UCODE_MAKEFILE:=$(firstword $(wildcard \
|
||||||
|
$(TOPDIR)/package/utils/ucode/Makefile \
|
||||||
|
$(TOPDIR)/package/feeds/base/ucode/Makefile \
|
||||||
|
$(TOPDIR)/feeds/base/package/utils/ucode/Makefile))
|
||||||
|
UCODE_BUILT:=$(if $(UCODE_MAKEFILE),$(subst -,.,$(shell sed -n 's/^PKG_SOURCE_DATE:=//p' '$(UCODE_MAKEFILE)')))
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# base: daemon + framework + codec/client + shared core. No backend on its own;
|
# base: daemon + framework + codec/client + shared core. No backend on its own;
|
||||||
# the daemon loads whichever wwand-qmi/-mbim/-ncm package is present.
|
# the daemon loads whichever wwand-qmi/-mbim/-ncm package is present.
|
||||||
@@ -152,6 +178,7 @@ define Package/wwand
|
|||||||
# wwand-private, always version-locked to the ucode side). PROVIDES keeps
|
# wwand-private, always version-locked to the ucode side). PROVIDES keeps
|
||||||
# configs/upgrades from the era of the separate ucode-mod-wwand-io working.
|
# configs/upgrades from the era of the separate ucode-mod-wwand-io working.
|
||||||
PROVIDES:=ucode-mod-wwand-io
|
PROVIDES:=ucode-mod-wwand-io
|
||||||
|
EXTRA_DEPENDS:=$(if $(UCODE_BUILT),ucode (>=$(UCODE_BUILT)))
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/wwand/description
|
define Package/wwand/description
|
||||||
|
|||||||
Reference in new issue
Block a user