mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 10:31:38 +08:00
🚀 Sync 2026-04-17 20:33:33
This commit is contained in:
parent
959aa5c65a
commit
f786b4b976
@ -5,12 +5,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=daed
|
||||
PKG_VERSION:=1.26.0
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=dae-lang-core-v0.2.0
|
||||
PKG_RELEASE:=4
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/daeuniverse/daed.git
|
||||
PKG_SOURCE_VERSION:=4982c709ef937fd19eced01b2ed07aad23bb56e8
|
||||
PKG_SOURCE_VERSION:=06857f52c33cc3264aea9eb90431afbe8909073c
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=AGPL-3.0-only MIT
|
||||
|
||||
@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
|
||||
|
||||
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.0)
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=3
|
||||
|
||||
LUCI_TITLE:=LuCI support for EasyTier
|
||||
LUCI_DEPENDS:=+kmod-tun +easytier +luci-compat
|
||||
|
||||
@ -74,6 +74,9 @@ function index()
|
||||
entry({"admin", "vpn", "easytier", "restart_service"}, call("restart_service")).leaf = true
|
||||
entry({"admin", "vpn", "easytier", "toggle_core"}, call("toggle_core")).leaf = true
|
||||
entry({"admin", "vpn", "easytier", "toggle_web"}, call("toggle_web")).leaf = true
|
||||
entry({"admin", "vpn", "easytier", "download_easytier"}, call("download_easytier")).leaf = true
|
||||
entry({"admin", "vpn", "easytier", "download_progress"}, call("download_progress")).leaf = true
|
||||
entry({"admin", "vpn", "easytier", "cancel_download"}, call("cancel_download")).leaf = true
|
||||
end
|
||||
|
||||
function act_status()
|
||||
@ -749,3 +752,389 @@ function toggle_web()
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({success = true})
|
||||
end
|
||||
-- 检测CPU架构
|
||||
local function detect_arch()
|
||||
local cputype = safe_exec("uname -ms | tr ' ' '_' | tr '[A-Z]' '[a-z]'")
|
||||
local cpucore = ""
|
||||
|
||||
if cputype:match("linux.*armv.*") then
|
||||
cpucore = "arm"
|
||||
end
|
||||
if cputype:match("linux.*armv7.*") and safe_exec("cat /proc/cpuinfo | grep vfp") ~= "" then
|
||||
cpucore = "armv7"
|
||||
end
|
||||
if cputype:match("linux.*aarch64.*") or cputype:match("linux.*armv8.*") then
|
||||
cpucore = "aarch64"
|
||||
end
|
||||
if cputype:match("linux.*86.*") then
|
||||
cpucore = "i386"
|
||||
end
|
||||
if cputype:match("linux.*86_64.*") then
|
||||
cpucore = "x86_64"
|
||||
end
|
||||
if cputype:match("linux.*mips.*") then
|
||||
local mipstype = safe_exec("echo -n I | hexdump -o 2>/dev/null | awk '{ print substr($2,6,1); exit}'")
|
||||
if mipstype == "0" then
|
||||
cpucore = "mips"
|
||||
else
|
||||
cpucore = "mipsel"
|
||||
end
|
||||
end
|
||||
|
||||
return cpucore
|
||||
end
|
||||
|
||||
-- 检查依赖
|
||||
local function check_dependencies()
|
||||
local arch = detect_arch()
|
||||
if arch == "" then
|
||||
return false, i18n.translate("Unable to detect CPU architecture")
|
||||
end
|
||||
if arch == "i386" then
|
||||
return false, i18n.translate("No x86-32bit program available")
|
||||
end
|
||||
if safe_exec("which unzip") == "" then
|
||||
return false, i18n.translate("System lacks unzip, cannot download and extract")
|
||||
end
|
||||
return true, arch
|
||||
end
|
||||
|
||||
-- 获取GitHub镜像列表
|
||||
local function get_github_proxies()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local proxies = {}
|
||||
|
||||
-- 从UCI配置读取代理列表
|
||||
local proxy_list = uci:get("easytier", "@easytier[0]", "github_proxys")
|
||||
|
||||
if proxy_list then
|
||||
if type(proxy_list) == "table" then
|
||||
-- 多个代理
|
||||
for _, proxy in ipairs(proxy_list) do
|
||||
if proxy and proxy ~= "" then
|
||||
table.insert(proxies, proxy)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- 单个代理
|
||||
if proxy_list ~= "" then
|
||||
table.insert(proxies, proxy_list)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 如果没有配置代理,使用默认值
|
||||
if #proxies == 0 then
|
||||
proxies = {
|
||||
"https://ghproxy.net/",
|
||||
"https://gh-proxy.com/",
|
||||
"https://cdn.gh-proxy.com/",
|
||||
"https://ghfast.top/"
|
||||
}
|
||||
end
|
||||
|
||||
-- 添加不使用代理的选项(空字符串表示直连)
|
||||
table.insert(proxies, "")
|
||||
|
||||
return proxies
|
||||
end
|
||||
|
||||
-- 下载文件
|
||||
local function download_file(url, output_path, progress_callback)
|
||||
local download_tools = {"curl", "wget"}
|
||||
|
||||
for _, tool in ipairs(download_tools) do
|
||||
if safe_exec("which " .. tool) ~= "" then
|
||||
local cmd = ""
|
||||
if tool == "curl" then
|
||||
cmd = string.format("curl -L -k --connect-timeout 30 --max-time 300 -o '%s' '%s'", output_path, url)
|
||||
elseif tool == "wget" then
|
||||
cmd = string.format("wget --no-check-certificate --timeout=30 --tries=3 -O '%s' '%s'", output_path, url)
|
||||
end
|
||||
|
||||
if progress_callback then
|
||||
progress_callback(50, i18n.translate("Downloading with") .. " " .. tool .. "...")
|
||||
end
|
||||
|
||||
local result = os.execute(cmd)
|
||||
if result == 0 and nixio.fs.access(output_path) then
|
||||
-- 检查文件大小,确保下载完整
|
||||
local stat = nixio.fs.stat(output_path)
|
||||
if stat and stat.size > 3 * 1024 * 1024 then -- 至少3MB
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function download_easytier()
|
||||
luci.http.prepare_content("application/json")
|
||||
|
||||
local json = require "luci.jsonc"
|
||||
local progress_file = "/tmp/easytier_download_progress"
|
||||
|
||||
-- 检查是否已有下载任务
|
||||
local existing_progress = safe_read_file(progress_file)
|
||||
if existing_progress then
|
||||
local progress_data = json.parse(existing_progress)
|
||||
if progress_data and progress_data.progress > 0 and progress_data.progress < 100 and not progress_data.error then
|
||||
luci.http.write_json({
|
||||
success = true,
|
||||
progress = progress_data.progress,
|
||||
message = i18n.translate("Download task already exists"),
|
||||
url = progress_data.url or ""
|
||||
})
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local req_data = json.parse(luci.http.content())
|
||||
if not req_data or not req_data.version then
|
||||
luci.http.write_json({success = false, message = i18n.translate("Missing version parameter")})
|
||||
return
|
||||
end
|
||||
|
||||
local version = req_data.version
|
||||
|
||||
-- 1. 检查依赖和架构
|
||||
local ok, arch_or_error = check_dependencies()
|
||||
if not ok then
|
||||
luci.http.write_json({success = false, message = arch_or_error})
|
||||
return
|
||||
end
|
||||
|
||||
local arch = arch_or_error
|
||||
local proxies = get_github_proxies()
|
||||
local download_dir = "/tmp/easytier_download"
|
||||
local zip_file = download_dir .. "/easytier-linux-" .. arch .. "-" .. version .. ".zip"
|
||||
|
||||
-- 创建下载目录
|
||||
os.execute("mkdir -p " .. download_dir)
|
||||
|
||||
-- 创建取消标志文件
|
||||
local cancel_file = "/tmp/easytier_download_cancel"
|
||||
os.execute("rm -f " .. cancel_file)
|
||||
|
||||
-- 创建进度文件标记任务开始
|
||||
local f = io.open(progress_file, "w")
|
||||
if f then
|
||||
f:write(json.stringify({
|
||||
progress = 1,
|
||||
message = "Downloading...",
|
||||
url = "",
|
||||
error = false
|
||||
}))
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- 检查是否被取消
|
||||
local function check_cancelled()
|
||||
return nixio.fs.access(cancel_file)
|
||||
end
|
||||
|
||||
-- 2. 循环尝试不同的镜像地址下载、解压、验证
|
||||
local download_success = false
|
||||
local download_url = ""
|
||||
local core_file, cli_file, web_file
|
||||
|
||||
for _, proxy in ipairs(proxies) do
|
||||
-- 检查是否被取消
|
||||
if check_cancelled() then
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
|
||||
return
|
||||
end
|
||||
|
||||
download_url = proxy .. "https://github.com/EasyTier/EasyTier/releases/download/" .. version .. "/easytier-linux-" .. arch .. "-" .. version .. ".zip"
|
||||
|
||||
-- 删除之前的失败文件
|
||||
os.execute("rm -f " .. zip_file)
|
||||
os.execute("rm -rf " .. download_dir .. "/extracted")
|
||||
|
||||
-- 下载
|
||||
if download_file(download_url, zip_file) then
|
||||
local stat = nixio.fs.stat(zip_file)
|
||||
if stat and stat.size > 10240 then
|
||||
-- 检查是否被取消
|
||||
if check_cancelled() then
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
|
||||
return
|
||||
end
|
||||
|
||||
-- 解压
|
||||
local extract_dir = download_dir .. "/extracted"
|
||||
os.execute("mkdir -p " .. extract_dir)
|
||||
local unzip_result = os.execute("cd " .. extract_dir .. " && unzip -o " .. zip_file .. " >/dev/null 2>&1")
|
||||
|
||||
if unzip_result == 0 then
|
||||
-- 检查是否被取消
|
||||
if check_cancelled() then
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
|
||||
return
|
||||
end
|
||||
|
||||
-- 查找解压后的目录(使用通配符)
|
||||
local find_cmd = "find " .. extract_dir .. " -maxdepth 1 -type d -name 'easytier-linux-*' 2>/dev/null | head -1"
|
||||
local handle = io.popen(find_cmd)
|
||||
local subdir = handle:read("*a"):match("^%s*(.-)%s*$")
|
||||
handle:close()
|
||||
|
||||
if subdir and subdir ~= "" then
|
||||
core_file = subdir .. "/easytier-core"
|
||||
cli_file = subdir .. "/easytier-cli"
|
||||
web_file = subdir .. "/easytier-web-embed"
|
||||
|
||||
local files_ok = nixio.fs.access(core_file) and nixio.fs.access(cli_file)
|
||||
if arch ~= "mips" and arch ~= "mipsel" then
|
||||
files_ok = files_ok and nixio.fs.access(web_file)
|
||||
end
|
||||
|
||||
if files_ok then
|
||||
-- 检查是否被取消
|
||||
if check_cancelled() then
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
|
||||
return
|
||||
end
|
||||
|
||||
-- 先赋予执行权限,再测试程序
|
||||
local test_files = {core_file, cli_file}
|
||||
if arch ~= "mips" and arch ~= "mipsel" then
|
||||
table.insert(test_files, web_file)
|
||||
end
|
||||
|
||||
local test_ok = true
|
||||
for _, file in ipairs(test_files) do
|
||||
os.execute("chmod +x " .. file)
|
||||
if not test_binary(file) then
|
||||
test_ok = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if test_ok then
|
||||
download_success = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 删除失败的文件,继续下一个地址
|
||||
os.execute("rm -f " .. zip_file)
|
||||
end
|
||||
|
||||
if not download_success then
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
luci.http.write_json({success = false, message = i18n.translate("All download attempts failed")})
|
||||
return
|
||||
end
|
||||
|
||||
-- 检查是否被取消(替换前)
|
||||
if check_cancelled() then
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
luci.http.write_json({success = false, message = i18n.translate("Download cancelled")})
|
||||
return
|
||||
end
|
||||
|
||||
-- 3. 从UCI获取路径并安装
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local easytierbin = uci:get_first("easytier", "easytier", "easytierbin") or "/usr/bin/easytier-core"
|
||||
local easytierwebbin = uci:get_first("easytier", "easytier", "easytierwebbin") or "/usr/bin/easytier-web"
|
||||
|
||||
local core_dir = easytierbin:match("^(.*/)[^/]+$") or "/usr/bin/"
|
||||
|
||||
local programs = {
|
||||
{src = core_file, dest = easytierbin},
|
||||
{src = cli_file, dest = core_dir .. "easytier-cli"}
|
||||
}
|
||||
|
||||
if arch ~= "mips" and arch ~= "mipsel" then
|
||||
table.insert(programs, {src = web_file, dest = easytierwebbin})
|
||||
end
|
||||
|
||||
-- 安装并验证
|
||||
for _, prog in ipairs(programs) do
|
||||
local result = os.execute("cp " .. prog.src .. " " .. prog.dest .. " 2>/dev/null")
|
||||
if result ~= 0 then
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
luci.http.write_json({success = false, message = i18n.translate("Failed to install program") .. ": " .. prog.dest})
|
||||
return
|
||||
end
|
||||
|
||||
os.execute("chmod +x " .. prog.dest)
|
||||
|
||||
-- 再次验证安装后的程序
|
||||
if not test_binary(prog.dest) then
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
luci.http.write_json({success = false, message = i18n.translate("Program is incomplete or architecture mismatch")})
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- 7. 清理临时文件和版本缓存
|
||||
os.execute("rm -rf " .. download_dir)
|
||||
os.execute("rm -f " .. cancel_file)
|
||||
os.execute("rm -f " .. progress_file)
|
||||
nixio.fs.remove("/tmp/easytier.tag")
|
||||
nixio.fs.remove("/tmp/easytierweb.tag")
|
||||
|
||||
luci.http.write_json({
|
||||
success = true,
|
||||
progress = 100,
|
||||
message = i18n.translate("Download and installation completed")
|
||||
})
|
||||
end
|
||||
|
||||
function download_progress()
|
||||
luci.http.prepare_content("application/json")
|
||||
|
||||
local progress_file = "/tmp/easytier_download_progress"
|
||||
local content = safe_read_file(progress_file)
|
||||
|
||||
if content then
|
||||
local json = require "luci.jsonc"
|
||||
local progress_data = json.parse(content)
|
||||
if progress_data then
|
||||
luci.http.write_json({
|
||||
success = true,
|
||||
progress = progress_data.progress or 0,
|
||||
message = progress_data.message or "",
|
||||
url = progress_data.url or "",
|
||||
error = progress_data.error or false
|
||||
})
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
luci.http.write_json({
|
||||
success = true,
|
||||
progress = 0,
|
||||
message = i18n.translate("Preparing..."),
|
||||
url = "",
|
||||
error = false
|
||||
})
|
||||
end
|
||||
function cancel_download()
|
||||
luci.http.prepare_content("application/json")
|
||||
|
||||
-- 创建取消标志文件
|
||||
os.execute("touch /tmp/easytier_download_cancel")
|
||||
|
||||
luci.http.write_json({success = true})
|
||||
end
|
||||
|
||||
@ -5,6 +5,9 @@ m.description = translate("A simple, secure, decentralized VPN solution for intr
|
||||
.. "Project URL: <a href=\"https://github.com/EasyTier/EasyTier\" target=\"_blank\">github.com/EasyTier/EasyTier</a> "
|
||||
.. "<a href=\"http://easytier.cn\" target=\"_blank\">Official Documentation</a> "
|
||||
.. "<a href=\"http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=jhP2Z4UsEZ8wvfGPLrs0VwLKn_uz0Q_p&authKey=OGKSQLfg61YPCpVQuvx%2BxE7hUKBVBEVi9PljrDKbHlle6xqOXx8sOwPPTncMambK&noverify=0&group_code=949700262\" target=\"_blank\">QQ Group</a> ")
|
||||
.. "<a href=\"https://github.com/EasyTier/EasyTier/releases\" target=\"_blank\"><img alt=\"\" src=\"https://img.shields.io/github/v/tag/EasyTier/EasyTier?include_prereleases&logo=github&label=pre-releases&link=https%3A%2F%2Fgithub.com%2FEasyTier%2FEasyTier%2Freleases\" style=\"vertical-align:middle;\"></a> "
|
||||
.. "<a href=\"https://github.com/EasyTier/EasyTier/releases\" target=\"_blank\"><img alt=\"\" src=\"https://img.shields.io/github/v/release/EasyTier/EasyTier?logo=github&link=https%3A%2F%2Fgithub.com%2FEasyTier%2FEasyTier%2Freleases\" style=\"vertical-align:middle;\"></a> "
|
||||
.. "<a href=\"https://github.com/EasyTier/luci-app-easytier/releases\" target=\"_blank\"><img alt=\"\" src=\"https://img.shields.io/github/v/release/EasyTier/luci-app-easytier?logo=openwrt&label=luci&link=https%3A%2F%2Fgithub.com%2FEasyTier%2Fluci-app-easytier%2Freleases\" style=\"vertical-align:middle;\"></a>"
|
||||
m.pageaction = false
|
||||
|
||||
-- 状态卡片
|
||||
|
||||
@ -35,7 +35,7 @@ local uci = require 'luci.model.uci'.cursor()
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 32px var(--card-shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
margin: 20px 0;
|
||||
margin: 20px 0 20px 0;
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@ -403,17 +403,72 @@ local uci = require 'luci.model.uci'.cursor()
|
||||
|
||||
/* Contributors Section */
|
||||
.et-contributors-section {
|
||||
margin-top: 24px;
|
||||
margin: 20px 0 20px 0;
|
||||
padding: 20px;
|
||||
background: var(--card-bg);
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 32px var(--card-shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.et-contributors-section:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 40px var(--card-shadow);
|
||||
}
|
||||
|
||||
.et-contributors-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.et-contributors-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.et-contributors-pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.et-contributors-dots {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.et-contributors-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--border-color);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.et-contributors-dot.active {
|
||||
background: var(--tab-active);
|
||||
width: 24px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.et-contributors-dot:hover {
|
||||
background: var(--tab-active);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.et-contributors-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.et-contributors-container {
|
||||
@ -422,6 +477,19 @@ local uci = require 'luci.model.uci'.cursor()
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
transition: opacity 0.8s ease, transform 0.8s ease;
|
||||
}
|
||||
|
||||
.et-contributors-container.active {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.et-contributors-container.exit {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
|
||||
.et-contributors-container a {
|
||||
@ -442,12 +510,32 @@ local uci = require 'luci.model.uci'.cursor()
|
||||
}
|
||||
|
||||
.et-contributors-container img:hover {
|
||||
border-color: var(--info);
|
||||
border-color: var(--tab-active);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.et-contributors-section {
|
||||
padding: 16px;
|
||||
margin: 16px 0 16px 0;
|
||||
}
|
||||
|
||||
.et-contributors-section:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.et-contributors-header {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.et-contributors-pagination {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.et-contributors-container {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.et-contributors-container img {
|
||||
@ -557,10 +645,18 @@ local uci = require 'luci.model.uci'.cursor()
|
||||
<%:Collecting data...%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contributors Section -->
|
||||
<div id="contributors-section" class="et-contributors-section" style="display: none;">
|
||||
</div>
|
||||
|
||||
<!-- Contributors Section -->
|
||||
<div id="contributors-section" class="et-contributors-section" style="display: none;">
|
||||
<div class="et-contributors-header">
|
||||
<div class="et-contributors-title"><%:Contributors%></div>
|
||||
<div class="et-contributors-pagination">
|
||||
<span id="contributors-page-info"></span>
|
||||
<div id="contributors-dots" class="et-contributors-dots"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="et-contributors-wrapper">
|
||||
<div id="contributors" class="et-contributors-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -700,6 +796,9 @@ var translations = {
|
||||
// VPN Portal特殊处理
|
||||
"ERROR: Wireguard VPN Portal Not Started": "<%:ERROR: Wireguard VPN Portal Not Started%>",
|
||||
|
||||
// 错误信息翻译
|
||||
"no running instances found": "<%:no running instances found%>",
|
||||
|
||||
// 统计指标翻译
|
||||
"compression_bytes_rx_before": "<%:Compression RX Before%>",
|
||||
"compression_bytes_rx_after": "<%:Compression RX After%>",
|
||||
@ -808,6 +907,11 @@ function parseTable(content, tabName) {
|
||||
return '<div class="et-error">' + escapeHtml(tr('ERROR: Wireguard VPN Portal Not Started')) + '</div>';
|
||||
}
|
||||
|
||||
// 未找到运行实例错误处理
|
||||
if (content.indexOf('no running instances found') !== -1) {
|
||||
return '<div class="et-error">' + escapeHtml(tr('no running instances found')) + '</div>';
|
||||
}
|
||||
|
||||
// Peer Center错误处理
|
||||
if (content.indexOf('Error: Invalid service name: "PeerCenterRpc"') !== -1) {
|
||||
return '<div class="et-error"><%:Peer Center service not available. This feature requires multiple nodes in the network.%></div>';
|
||||
@ -1104,56 +1208,215 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
refreshConnInfo();
|
||||
});
|
||||
|
||||
// 加载贡献者头像 (ES5)
|
||||
function loadContributors() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://api.github.com/repos/EasyTier/EasyTier/contributors', true);
|
||||
xhr.timeout = 5000; // 5秒超时
|
||||
// 贡献者轮播管理 (ES5)
|
||||
var contributorsCarousel = {
|
||||
allContributors: [],
|
||||
currentPage: 0,
|
||||
pageSize: 30,
|
||||
totalPages: 0,
|
||||
autoPlayTimer: null,
|
||||
autoPlayInterval: 3000,
|
||||
container: null,
|
||||
section: null,
|
||||
pageInfo: null,
|
||||
dotsContainer: null,
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
var container = document.getElementById('contributors');
|
||||
var section = document.getElementById('contributors-section');
|
||||
|
||||
if (!container || !section || !data.length) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var user = data[i];
|
||||
var a = document.createElement('a');
|
||||
a.href = user.html_url;
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener noreferrer';
|
||||
|
||||
var img = document.createElement('img');
|
||||
img.src = user.avatar_url;
|
||||
img.alt = user.login;
|
||||
img.title = user.login + ' (' + user.contributions + ' contributions)';
|
||||
|
||||
a.appendChild(img);
|
||||
container.appendChild(a);
|
||||
init: function() {
|
||||
this.container = document.getElementById('contributors');
|
||||
this.section = document.getElementById('contributors-section');
|
||||
this.pageInfo = document.getElementById('contributors-page-info');
|
||||
this.dotsContainer = document.getElementById('contributors-dots');
|
||||
this.setupHoverPause();
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
setupHoverPause: function() {
|
||||
var self = this;
|
||||
if (!this.section) return;
|
||||
|
||||
this.section.addEventListener('mouseenter', function() {
|
||||
self.stopAutoPlay();
|
||||
});
|
||||
|
||||
this.section.addEventListener('mouseleave', function() {
|
||||
self.startAutoPlay();
|
||||
});
|
||||
|
||||
// 移动端滑动支持
|
||||
this.setupTouchSwipe();
|
||||
},
|
||||
|
||||
setupTouchSwipe: function() {
|
||||
var self = this;
|
||||
if (!this.container) return;
|
||||
|
||||
var startX = 0;
|
||||
var startY = 0;
|
||||
var threshold = 50; // 滑动阈值
|
||||
|
||||
this.container.addEventListener('touchstart', function(e) {
|
||||
self.stopAutoPlay();
|
||||
startX = e.touches[0].clientX;
|
||||
startY = e.touches[0].clientY;
|
||||
});
|
||||
|
||||
this.container.addEventListener('touchend', function(e) {
|
||||
var endX = e.changedTouches[0].clientX;
|
||||
var endY = e.changedTouches[0].clientY;
|
||||
var deltaX = endX - startX;
|
||||
var deltaY = endY - startY;
|
||||
|
||||
// 确保是水平滑动
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > threshold) {
|
||||
if (deltaX > 0) {
|
||||
// 右滑 - 上一页
|
||||
var prevPage = (self.currentPage - 1 + self.totalPages) % self.totalPages;
|
||||
self.showPage(prevPage);
|
||||
} else {
|
||||
// 左滑 - 下一页
|
||||
self.nextPage();
|
||||
}
|
||||
|
||||
// 成功加载后显示卡片
|
||||
section.style.display = 'block';
|
||||
} catch (e) {
|
||||
// 失败时保持隐藏
|
||||
}
|
||||
|
||||
self.startAutoPlay();
|
||||
});
|
||||
},
|
||||
|
||||
loadData: function() {
|
||||
var self = this;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://api.github.com/repos/EasyTier/EasyTier/contributors?per_page=100', true);
|
||||
xhr.timeout = 10000;
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
if (!data || data.length === 0) return;
|
||||
|
||||
self.allContributors = data;
|
||||
self.totalPages = Math.ceil(data.length / self.pageSize);
|
||||
self.renderDots();
|
||||
self.showPage(0);
|
||||
self.section.style.display = 'block';
|
||||
self.startAutoPlay();
|
||||
} catch (e) {
|
||||
console.error('Failed to parse contributors:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
console.error('Failed to load contributors');
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
console.error('Contributors request timeout');
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
},
|
||||
|
||||
renderDots: function() {
|
||||
if (!this.dotsContainer) return;
|
||||
this.dotsContainer.innerHTML = '';
|
||||
|
||||
for (var i = 0; i < this.totalPages; i++) {
|
||||
var dot = document.createElement('div');
|
||||
dot.className = 'et-contributors-dot';
|
||||
dot.setAttribute('data-page', i);
|
||||
|
||||
var self = this;
|
||||
(function(page) {
|
||||
dot.onclick = function() {
|
||||
self.stopAutoPlay();
|
||||
self.showPage(page);
|
||||
self.startAutoPlay();
|
||||
};
|
||||
})(i);
|
||||
|
||||
this.dotsContainer.appendChild(dot);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
xhr.onerror = function() {
|
||||
// 失败时保持隐藏
|
||||
};
|
||||
showPage: function(pageIndex) {
|
||||
var self = this;
|
||||
if (pageIndex < 0 || pageIndex >= this.totalPages) return;
|
||||
|
||||
// 退出动画
|
||||
this.container.classList.remove('active');
|
||||
this.container.classList.add('exit');
|
||||
|
||||
setTimeout(function() {
|
||||
self.currentPage = pageIndex;
|
||||
var start = pageIndex * self.pageSize;
|
||||
var end = Math.min(start + self.pageSize, self.allContributors.length);
|
||||
var pageData = self.allContributors.slice(start, end);
|
||||
|
||||
// 更新内容
|
||||
self.container.innerHTML = '';
|
||||
for (var i = 0; i < pageData.length; i++) {
|
||||
var user = pageData[i];
|
||||
var a = document.createElement('a');
|
||||
a.href = user.html_url;
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener noreferrer';
|
||||
|
||||
var img = document.createElement('img');
|
||||
img.src = user.avatar_url;
|
||||
img.alt = user.login;
|
||||
img.title = user.login + ' (' + user.contributions + ' contributions)';
|
||||
|
||||
a.appendChild(img);
|
||||
self.container.appendChild(a);
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
if (self.pageInfo) {
|
||||
self.pageInfo.textContent = (pageIndex + 1) + ' / ' + self.totalPages;
|
||||
}
|
||||
|
||||
// 更新指示器
|
||||
var dots = self.dotsContainer.querySelectorAll('.et-contributors-dot');
|
||||
for (var j = 0; j < dots.length; j++) {
|
||||
dots[j].classList.remove('active');
|
||||
}
|
||||
if (dots[pageIndex]) {
|
||||
dots[pageIndex].classList.add('active');
|
||||
}
|
||||
|
||||
// 进入动画
|
||||
self.container.classList.remove('exit');
|
||||
setTimeout(function() {
|
||||
self.container.classList.add('active');
|
||||
}, 50);
|
||||
}, 600);
|
||||
},
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
// 超时时保持隐藏
|
||||
};
|
||||
nextPage: function() {
|
||||
var next = (this.currentPage + 1) % this.totalPages;
|
||||
this.showPage(next);
|
||||
},
|
||||
|
||||
xhr.send();
|
||||
startAutoPlay: function() {
|
||||
var self = this;
|
||||
this.stopAutoPlay();
|
||||
this.autoPlayTimer = setInterval(function() {
|
||||
self.nextPage();
|
||||
}, this.autoPlayInterval);
|
||||
},
|
||||
|
||||
stopAutoPlay: function() {
|
||||
if (this.autoPlayTimer) {
|
||||
clearInterval(this.autoPlayTimer);
|
||||
this.autoPlayTimer = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 加载贡献者
|
||||
function loadContributors() {
|
||||
contributorsCarousel.init();
|
||||
}
|
||||
|
||||
// 自动刷新
|
||||
|
||||
@ -293,6 +293,9 @@ local translate = i18n.translate
|
||||
</button>
|
||||
</div>
|
||||
<div class="log-controls">
|
||||
<a href="https://deepwiki.com/EasyTier/EasyTier" target="_blank" style="display:inline-block;height:36px;line-height:36px;margin-right:8px;">
|
||||
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki" style="vertical-align:middle;">
|
||||
</a>
|
||||
<select id="log_level_filter" class="log-select" onchange="filterLogLevel()">
|
||||
<option value="all"><%=translate("All Levels")%></option>
|
||||
<option value="INFO">INFO</option>
|
||||
|
||||
@ -369,10 +369,26 @@ local translate = i18n.translate
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:16px;">
|
||||
<label style="display:block;font-size:13px;font-weight:500;color:var(--text-primary);margin-bottom:4px;">
|
||||
<%=translate("Fallback Version")%>
|
||||
<label style="display:flex;align-items:center;font-size:13px;font-weight:500;color:var(--text-primary);margin-bottom:4px;">
|
||||
<span><%=translate("Fallback Version")%></span>
|
||||
<a href="https://github.com/EasyTier/EasyTier/releases" target="_blank" style="margin-left:8px;line-height:1;">
|
||||
<img alt="" src="https://img.shields.io/github/v/tag/EasyTier/EasyTier?include_prereleases&logo=github&label=pre-releases&link=https%3A%2F%2Fgithub.com%2FEasyTier%2FEasyTier%2Freleases" style="height:13px;vertical-align:middle;">
|
||||
</a>
|
||||
<a href="https://github.com/EasyTier/EasyTier/releases" target="_blank" style="margin-left:4px;line-height:1;">
|
||||
<img alt="" src="https://img.shields.io/github/v/release/EasyTier/EasyTier?logo=github&link=https%3A%2F%2Fgithub.com%2FEasyTier%2FEasyTier%2Freleases" style="height:13px;vertical-align:middle;">
|
||||
</a>
|
||||
</label>
|
||||
<input type="text" id="fallback_version" class="config-input" placeholder="v2.5.0">
|
||||
<div id="version-selector-container" style="display:flex;gap:8px;align-items:center;">
|
||||
<div style="flex:1;">
|
||||
<select id="fallback_version_select" class="config-input" style="display:none;width:100%;">
|
||||
<option value=""><%=translate("Loading versions...")%></option>
|
||||
</select>
|
||||
<input type="text" id="fallback_version" class="config-input" placeholder="v2.6.0" style="display:none;width:100%;">
|
||||
</div>
|
||||
<button type="button" class="upload-btn" onclick="showDownloadModal()" style="padding:8px 16px;min-width:auto;flex-shrink:0;">
|
||||
<%=translate("Online Download")%>
|
||||
</button>
|
||||
</div>
|
||||
<div style="font-size:11px;color:#999;opacity:0.8;margin-top:4px;">
|
||||
<%=translate("Used when unable to fetch the latest version during online download")%>
|
||||
</div>
|
||||
@ -408,6 +424,39 @@ local translate = i18n.translate
|
||||
|
||||
<div class="alert" id="alert_box"></div>
|
||||
|
||||
<!-- 在线下载模态窗口 -->
|
||||
<div id="download_modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3><%=translate("Online Download EasyTier")%></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div style="margin-bottom:16px;">
|
||||
<label style="display:block;font-size:13px;font-weight:500;color:var(--text-primary);margin-bottom:4px;">
|
||||
<%=translate("Download Version")%>
|
||||
</label>
|
||||
<div id="download-version-container">
|
||||
<!-- 这里会动态插入备用版本的输入框 -->
|
||||
</div>
|
||||
<div style="font-size:11px;color:#999;opacity:0.8;margin-top:4px;">
|
||||
<%=translate("Download specified version from GitHub and install to specified path")%>
|
||||
</div>
|
||||
</div>
|
||||
<div class="download-progress" id="download_progress" style="display:none;">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="download_progress_fill"></div>
|
||||
</div>
|
||||
<div class="progress-text" id="download_progress_text"><%=translate("Downloading...")%> 0%</div>
|
||||
<div style="font-size:11px;color:#666;margin-top:4px;word-break:break-all;overflow-wrap:break-word;" id="download_url_text"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="text-align:right;">
|
||||
<button class="upload-btn secondary" onclick="closeDownloadModal()"><%=translate("Close")%></button>
|
||||
<button class="upload-btn" onclick="startDownload()" id="download_confirm_btn"><%=translate("Download")%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="upload-info">
|
||||
<div class="upload-info-title"><%=translate("Supported Files")%></div>
|
||||
<ul class="upload-info-list">
|
||||
@ -457,6 +506,243 @@ local translate = i18n.translate
|
||||
var originalConfig = {};
|
||||
|
||||
// 加载配置
|
||||
// 版本管理
|
||||
var versionManager = {
|
||||
versions: [],
|
||||
isLoaded: false,
|
||||
customValue: '', // 跟踪用户自定义输入
|
||||
|
||||
init: function() {
|
||||
this.loadVersions();
|
||||
},
|
||||
|
||||
loadVersions: function() {
|
||||
var self = this;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://api.github.com/repos/EasyTier/EasyTier/releases', true);
|
||||
xhr.timeout = 10000;
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var releases = JSON.parse(xhr.responseText);
|
||||
self.versions = releases.map(function(release) {
|
||||
return {
|
||||
tag: release.tag_name,
|
||||
name: release.name || release.tag_name,
|
||||
published: release.published_at,
|
||||
prerelease: release.prerelease
|
||||
};
|
||||
});
|
||||
self.isLoaded = true;
|
||||
self.renderVersionSelect();
|
||||
// 版本加载完成后,重新应用配置中的版本
|
||||
if (self.pendingVersion) {
|
||||
self.setVersion(self.pendingVersion);
|
||||
self.pendingVersion = null;
|
||||
}
|
||||
} catch (e) {
|
||||
self.fallbackToInput();
|
||||
}
|
||||
} else {
|
||||
self.fallbackToInput();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
self.fallbackToInput();
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
self.fallbackToInput();
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
},
|
||||
|
||||
renderVersionSelect: function() {
|
||||
var select = document.getElementById('fallback_version_select');
|
||||
var input = document.getElementById('fallback_version');
|
||||
|
||||
if (!select || !input) return;
|
||||
|
||||
select.innerHTML = '';
|
||||
|
||||
// 添加版本选项
|
||||
for (var i = 0; i < this.versions.length; i++) {
|
||||
var version = this.versions[i];
|
||||
var option = document.createElement('option');
|
||||
option.value = version.tag;
|
||||
var label = version.prerelease ?
|
||||
'<%=translate("Pre-release")%>' :
|
||||
'<%=translate("Stable")%>';
|
||||
option.textContent = version.tag + ' (' + label + ')';
|
||||
select.appendChild(option);
|
||||
}
|
||||
|
||||
// 添加自定义输入选项(最后一个)
|
||||
this.updateCustomOption(select);
|
||||
|
||||
// 显示选择器
|
||||
select.style.display = 'block';
|
||||
input.style.display = 'none';
|
||||
|
||||
// 绑定事件
|
||||
var self = this;
|
||||
select.onchange = function() {
|
||||
if (this.value === 'custom') {
|
||||
input.value = self.customValue; // 恢复自定义值
|
||||
input.style.display = 'block';
|
||||
select.style.display = 'none';
|
||||
input.focus();
|
||||
} else {
|
||||
input.value = this.value;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理点击已选中的自定义输入选项
|
||||
select.addEventListener('click', function(e) {
|
||||
// 检查点击的是否是自定义输入选项
|
||||
if (e.target.tagName === 'OPTION' && e.target.value === 'custom' && this.value === 'custom') {
|
||||
input.value = self.customValue; // 恢复自定义值
|
||||
input.style.display = 'block';
|
||||
select.style.display = 'none';
|
||||
input.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// 输入框事件
|
||||
input.oninput = function() {
|
||||
// 实时更新自定义值
|
||||
self.customValue = this.value;
|
||||
};
|
||||
|
||||
input.onblur = function() {
|
||||
// 失去焦点时更新显示格式
|
||||
if (self.isLoaded) {
|
||||
self.updateCustomOption(select);
|
||||
|
||||
var currentValue = input.value;
|
||||
if (self.isValidVersion(currentValue)) {
|
||||
select.value = currentValue;
|
||||
} else {
|
||||
select.value = 'custom';
|
||||
// 输入框显示为 "值 (自定义输入)" 格式
|
||||
if (currentValue && currentValue.trim() !== '') {
|
||||
input.value = currentValue + ' (<%=translate("Custom Input")%>)';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
input.onclick = function() {
|
||||
// 只有在显示格式状态时才弹出选择器
|
||||
var displayValue = input.value;
|
||||
if (displayValue.indexOf(' (') !== -1 && displayValue.indexOf(')') !== -1) {
|
||||
// 当前是显示格式,弹出选择器
|
||||
if (self.isLoaded) {
|
||||
var actualValue = self.customValue || displayValue.replace(/ \(.*\)$/, '');
|
||||
input.value = actualValue; // 恢复原始值
|
||||
|
||||
self.updateCustomOption(select);
|
||||
|
||||
// 选择当前值
|
||||
if (self.isValidVersion(actualValue)) {
|
||||
select.value = actualValue;
|
||||
} else {
|
||||
select.value = 'custom';
|
||||
}
|
||||
|
||||
select.style.display = 'block';
|
||||
input.style.display = 'none';
|
||||
}
|
||||
}
|
||||
// 如果已经是编辑状态(没有格式化显示),不做任何处理
|
||||
};
|
||||
},
|
||||
|
||||
updateCustomOption: function(select) {
|
||||
// 移除旧的自定义选项
|
||||
var oldCustom = select.querySelector('option[value="custom"]');
|
||||
if (oldCustom) {
|
||||
oldCustom.remove();
|
||||
}
|
||||
|
||||
// 添加新的自定义选项
|
||||
var customOption = document.createElement('option');
|
||||
customOption.value = 'custom';
|
||||
|
||||
if (this.customValue && this.customValue.trim() !== '' && !this.isValidVersion(this.customValue)) {
|
||||
// 显示用户自定义的值
|
||||
customOption.textContent = this.customValue + ' (<%=translate("Custom Input")%>)';
|
||||
} else {
|
||||
// 默认显示
|
||||
customOption.textContent = '<%=translate("Custom Input")%>';
|
||||
}
|
||||
|
||||
select.appendChild(customOption);
|
||||
},
|
||||
|
||||
fallbackToInput: function() {
|
||||
var select = document.getElementById('fallback_version_select');
|
||||
var input = document.getElementById('fallback_version');
|
||||
|
||||
if (select && input) {
|
||||
select.style.display = 'none';
|
||||
input.style.display = 'block';
|
||||
}
|
||||
},
|
||||
|
||||
isValidVersion: function(version) {
|
||||
for (var i = 0; i < this.versions.length; i++) {
|
||||
if (this.versions[i].tag === version) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getCurrentVersion: function() {
|
||||
var input = document.getElementById('fallback_version');
|
||||
// 始终返回输入框的实际值,无论是选择的还是自定义输入的
|
||||
return input ? input.value : '';
|
||||
},
|
||||
|
||||
setVersion: function(version) {
|
||||
var select = document.getElementById('fallback_version_select');
|
||||
var input = document.getElementById('fallback_version');
|
||||
|
||||
if (!select || !input) return;
|
||||
|
||||
input.value = version;
|
||||
// 如果版本不在列表中,设置为自定义值
|
||||
if (!this.isValidVersion(version)) {
|
||||
this.customValue = version;
|
||||
}
|
||||
|
||||
if (this.isLoaded) {
|
||||
// 更新自定义选项显示
|
||||
this.updateCustomOption(select);
|
||||
|
||||
// 版本列表已加载,显示下拉选择器
|
||||
if (this.isValidVersion(version)) {
|
||||
// 版本在列表中,直接选中
|
||||
select.value = version;
|
||||
} else {
|
||||
// 版本不在列表中,选择自定义输入
|
||||
select.value = 'custom';
|
||||
}
|
||||
select.style.display = 'block';
|
||||
input.style.display = 'none';
|
||||
} else {
|
||||
// 版本列表未加载,保存待处理的版本
|
||||
this.pendingVersion = version;
|
||||
select.style.display = 'none';
|
||||
input.style.display = 'block';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function loadConfig() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '<%=url("admin", "vpn", "easytier", "get_upload_config")%>', true);
|
||||
@ -472,7 +758,7 @@ function loadConfig() {
|
||||
proxyList = Array.isArray(proxys) ? proxys : [];
|
||||
|
||||
renderProxyList();
|
||||
document.getElementById('fallback_version').value = config.fallback_version || 'v2.6.0';
|
||||
versionManager.setVersion(config.fallback_version || 'v2.6.0');
|
||||
|
||||
// 保存原始配置
|
||||
originalConfig = {
|
||||
@ -523,13 +809,19 @@ function checkDiskSpace(inputId) {
|
||||
if (inputId === 'easytierbin') {
|
||||
if (response.core_size) {
|
||||
sizeHtml += 'easytier-core ' + response.core_size;
|
||||
} else {
|
||||
sizeHtml += '<span style="color:var(--error);opacity:0.6;">easytier-core: <%=translate("Program not found")%></span>';
|
||||
}
|
||||
if (response.cli_size) {
|
||||
sizeHtml += (sizeHtml ? ' | ' : '') + ' easytier-cli ' + response.cli_size;
|
||||
sizeHtml += (sizeHtml ? ' | ' : '') + 'easytier-cli ' + response.cli_size;
|
||||
} else {
|
||||
sizeHtml += (sizeHtml ? ' | ' : '') + '<span style="color:var(--error);opacity:0.6;">easytier-cli: <%=translate("Program not found")%></span>';
|
||||
}
|
||||
} else if (inputId === 'webbin') {
|
||||
if (response.web_size) {
|
||||
sizeHtml = 'easytier-web ' + response.web_size;
|
||||
} else {
|
||||
sizeHtml = '<span style="color:var(--error);opacity:0.6;">easytier-web: <%=translate("Program not found")%></span>';
|
||||
}
|
||||
}
|
||||
sizeDiv.innerHTML = sizeHtml ? '<span style="color:#999;opacity:0.8;"><%=translate("Program size")%>: ' + sizeHtml + '</span>' : '';
|
||||
@ -551,7 +843,7 @@ function getCurrentConfig() {
|
||||
easytierbin: document.getElementById('easytierbin').value || '/usr/bin/easytier-core',
|
||||
webbin: document.getElementById('webbin').value || '/usr/bin/easytier-web',
|
||||
github_proxys: JSON.stringify(proxyList),
|
||||
fallback_version: document.getElementById('fallback_version').value || 'v2.6.0'
|
||||
fallback_version: versionManager.getCurrentVersion() || 'v2.6.0'
|
||||
};
|
||||
}
|
||||
|
||||
@ -576,7 +868,7 @@ function saveConfig() {
|
||||
easytierbin: document.getElementById('easytierbin').value || '/usr/bin/easytier-core',
|
||||
webbin: document.getElementById('webbin').value || '/usr/bin/easytier-web',
|
||||
github_proxys: proxyList,
|
||||
fallback_version: document.getElementById('fallback_version').value || 'v2.6.0'
|
||||
fallback_version: versionManager.getCurrentVersion() || 'v2.6.0'
|
||||
};
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
@ -669,8 +961,6 @@ window.onload = function() {
|
||||
alertBox = document.getElementById('alert_box');
|
||||
restartModal = document.getElementById('restart_modal');
|
||||
|
||||
loadConfig();
|
||||
|
||||
var validNames = ['easytier-core', 'easytier-cli', 'easytier-web', 'easytier-web-embed'];
|
||||
|
||||
// 拖放事件
|
||||
@ -821,6 +1111,16 @@ function closeModal() {
|
||||
restartModal.classList.remove('show');
|
||||
}
|
||||
|
||||
function showRestartModal() {
|
||||
var modalTitle = document.querySelector('#restart_modal .modal-title');
|
||||
var modalMessage = document.getElementById('modal_message');
|
||||
|
||||
modalTitle.textContent = '<%=translate("Download Successful")%>';
|
||||
modalMessage.textContent = '<%=translate("Download completed. Restart service now?")%>';
|
||||
|
||||
restartModal.classList.add('show');
|
||||
}
|
||||
|
||||
function restartService() {
|
||||
closeModal();
|
||||
showAlert('info', '<%=translate("Restarting service...")%>');
|
||||
@ -841,6 +1141,378 @@ function restartService() {
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
// 在线下载功能
|
||||
function showDownloadModal() {
|
||||
var modal = document.getElementById('download_modal');
|
||||
var downloadContainer = document.getElementById('download-version-container');
|
||||
var originalContainer = document.getElementById('version-selector-container');
|
||||
|
||||
// 复制备用版本输入框到模态窗口
|
||||
var clonedContainer = originalContainer.cloneNode(true);
|
||||
clonedContainer.id = 'download-version-selector-container';
|
||||
|
||||
// 移除在线下载按钮
|
||||
var downloadBtn = clonedContainer.querySelector('button');
|
||||
if (downloadBtn) {
|
||||
downloadBtn.remove();
|
||||
}
|
||||
|
||||
// 调整样式,移除flex布局
|
||||
clonedContainer.style.display = 'block';
|
||||
var innerDiv = clonedContainer.querySelector('div[style*="flex:1"]');
|
||||
if (innerDiv) {
|
||||
innerDiv.style.flex = 'none';
|
||||
}
|
||||
|
||||
// 更新ID避免冲突
|
||||
var select = clonedContainer.querySelector('select');
|
||||
var input = clonedContainer.querySelector('input');
|
||||
if (select) select.id = 'download_fallback_version_select';
|
||||
if (input) input.id = 'download_fallback_version';
|
||||
|
||||
// 清空容器并插入克隆的输入框
|
||||
downloadContainer.innerHTML = '';
|
||||
downloadContainer.appendChild(clonedContainer);
|
||||
|
||||
// 重新绑定事件处理函数
|
||||
if (select && input && versionManager.isLoaded) {
|
||||
// 复制版本选项
|
||||
select.innerHTML = '';
|
||||
for (var i = 0; i < versionManager.versions.length; i++) {
|
||||
var version = versionManager.versions[i];
|
||||
var option = document.createElement('option');
|
||||
option.value = version.tag;
|
||||
var label = version.prerelease ?
|
||||
'<%=translate("Pre-release")%>' :
|
||||
'<%=translate("Stable")%>';
|
||||
option.textContent = version.tag + ' (' + label + ')';
|
||||
select.appendChild(option);
|
||||
}
|
||||
|
||||
// 添加自定义输入选项
|
||||
var customOption = document.createElement('option');
|
||||
customOption.value = 'custom';
|
||||
if (versionManager.customValue && versionManager.customValue.trim() !== '' && !versionManager.isValidVersion(versionManager.customValue)) {
|
||||
customOption.textContent = versionManager.customValue + ' (<%=translate("Custom Input")%>)';
|
||||
} else {
|
||||
customOption.textContent = '<%=translate("Custom Input")%>';
|
||||
}
|
||||
select.appendChild(customOption);
|
||||
|
||||
// 绑定事件
|
||||
select.onchange = function() {
|
||||
if (this.value === 'custom') {
|
||||
input.value = versionManager.customValue || '';
|
||||
input.style.display = 'block';
|
||||
select.style.display = 'none';
|
||||
input.focus();
|
||||
} else {
|
||||
input.value = this.value;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理点击已选中的自定义输入选项
|
||||
select.addEventListener('click', function(e) {
|
||||
if (e.target.tagName === 'OPTION' && e.target.value === 'custom' && this.value === 'custom') {
|
||||
input.value = versionManager.customValue || '';
|
||||
input.style.display = 'block';
|
||||
select.style.display = 'none';
|
||||
input.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// 输入框事件
|
||||
input.oninput = function() {
|
||||
versionManager.customValue = this.value;
|
||||
};
|
||||
|
||||
input.onblur = function() {
|
||||
if (versionManager.isLoaded) {
|
||||
// 更新自定义选项显示
|
||||
var oldCustom = select.querySelector('option[value="custom"]');
|
||||
if (oldCustom) {
|
||||
oldCustom.remove();
|
||||
}
|
||||
|
||||
var customOption = document.createElement('option');
|
||||
customOption.value = 'custom';
|
||||
if (versionManager.customValue && versionManager.customValue.trim() !== '' && !versionManager.isValidVersion(versionManager.customValue)) {
|
||||
customOption.textContent = versionManager.customValue + ' (<%=translate("Custom Input")%>)';
|
||||
input.value = versionManager.customValue + ' (<%=translate("Custom Input")%>)';
|
||||
} else {
|
||||
customOption.textContent = '<%=translate("Custom Input")%>';
|
||||
}
|
||||
select.appendChild(customOption);
|
||||
|
||||
// 设置选择器的当前值
|
||||
var currentValue = versionManager.customValue || input.value.replace(/ \(.*\)$/, '');
|
||||
if (versionManager.isValidVersion(currentValue)) {
|
||||
select.value = currentValue;
|
||||
} else {
|
||||
select.value = 'custom';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
input.onclick = function() {
|
||||
var displayValue = input.value;
|
||||
if (displayValue.indexOf(' (') !== -1 && displayValue.indexOf(')') !== -1) {
|
||||
if (versionManager.isLoaded) {
|
||||
var actualValue = versionManager.customValue || displayValue.replace(/ \(.*\)$/, '');
|
||||
input.value = actualValue;
|
||||
|
||||
// 更新自定义选项
|
||||
var oldCustom = select.querySelector('option[value="custom"]');
|
||||
if (oldCustom) {
|
||||
oldCustom.remove();
|
||||
}
|
||||
|
||||
var customOption = document.createElement('option');
|
||||
customOption.value = 'custom';
|
||||
if (versionManager.customValue && versionManager.customValue.trim() !== '' && !versionManager.isValidVersion(versionManager.customValue)) {
|
||||
customOption.textContent = versionManager.customValue + ' (<%=translate("Custom Input")%>)';
|
||||
} else {
|
||||
customOption.textContent = '<%=translate("Custom Input")%>';
|
||||
}
|
||||
select.appendChild(customOption);
|
||||
|
||||
if (versionManager.isValidVersion(actualValue)) {
|
||||
select.value = actualValue;
|
||||
} else {
|
||||
select.value = 'custom';
|
||||
}
|
||||
|
||||
select.style.display = 'block';
|
||||
input.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 同步当前值
|
||||
var currentValue = versionManager.getCurrentVersion();
|
||||
if (input) {
|
||||
input.value = currentValue;
|
||||
// 如果版本列表未加载,显示输入框
|
||||
if (!versionManager.isLoaded) {
|
||||
input.style.display = 'block';
|
||||
if (select) select.style.display = 'none';
|
||||
}
|
||||
}
|
||||
if (select && versionManager.isLoaded) {
|
||||
if (versionManager.isValidVersion(currentValue)) {
|
||||
select.value = currentValue;
|
||||
select.style.display = 'block';
|
||||
input.style.display = 'none';
|
||||
} else {
|
||||
select.value = 'custom';
|
||||
select.style.display = 'block';
|
||||
input.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
modal.classList.add('show');
|
||||
|
||||
// 检查是否有现有下载任务
|
||||
checkExistingDownload();
|
||||
}
|
||||
|
||||
function checkExistingDownload() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '<%=url("admin", "vpn", "easytier", "download_progress")%>', true);
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.success && response.progress > 0 && response.progress < 100 && !response.error) {
|
||||
// 有进行中的任务,显示进度条和切换按钮
|
||||
var progress = document.getElementById('download_progress');
|
||||
var progressFill = document.getElementById('download_progress_fill');
|
||||
var progressText = document.getElementById('download_progress_text');
|
||||
var confirmBtn = document.getElementById('download_confirm_btn');
|
||||
|
||||
progress.style.display = 'block';
|
||||
progressFill.style.width = '50%';
|
||||
progressText.textContent = '<%=translate("Downloading...")%> 50%';
|
||||
|
||||
// 切换按钮为取消下载
|
||||
confirmBtn.textContent = '<%=translate("Cancel Download")%>';
|
||||
confirmBtn.onclick = function() { cancelDownload(); };
|
||||
|
||||
showAlert('info', '<%=translate("Download task already exists, continuing...")%>');
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function closeDownloadModal() {
|
||||
var modal = document.getElementById('download_modal');
|
||||
modal.classList.remove('show');
|
||||
|
||||
// 重置进度条
|
||||
var progress = document.getElementById('download_progress');
|
||||
progress.style.display = 'none';
|
||||
|
||||
// 清空下载版本容器
|
||||
var downloadContainer = document.getElementById('download-version-container');
|
||||
downloadContainer.innerHTML = '';
|
||||
|
||||
// 恢复按钮
|
||||
var confirmBtn = document.getElementById('download_confirm_btn');
|
||||
if (confirmBtn) {
|
||||
confirmBtn.textContent = '<%=translate("Download")%>';
|
||||
confirmBtn.onclick = function() { startDownload(); };
|
||||
}
|
||||
}
|
||||
|
||||
var downloadCheckTimer = null;
|
||||
|
||||
function cancelDownload() {
|
||||
// 调用后端取消下载
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '<%=url("admin", "vpn", "easytier", "cancel_download")%>', true);
|
||||
xhr.onload = function() {
|
||||
closeDownloadModal();
|
||||
showAlert('info', '<%=translate("Download cancelled")%>');
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function startDownload() {
|
||||
// 从模态窗口中的输入框获取版本
|
||||
var input = document.getElementById('download_fallback_version');
|
||||
var version = input ? input.value.trim() : '';
|
||||
|
||||
// 移除显示格式,获取实际版本
|
||||
if (version.indexOf(' (') !== -1) {
|
||||
version = version.replace(/ \(.*\)$/, '');
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
showAlert('error', '<%=translate("Please enter download version")%>');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示进度条
|
||||
var progress = document.getElementById('download_progress');
|
||||
var progressFill = document.getElementById('download_progress_fill');
|
||||
var progressText = document.getElementById('download_progress_text');
|
||||
var urlText = document.getElementById('download_url_text');
|
||||
var confirmBtn = document.getElementById('download_confirm_btn');
|
||||
|
||||
progress.style.display = 'block';
|
||||
progressFill.style.width = '0%';
|
||||
progressFill.style.backgroundColor = '';
|
||||
progressText.textContent = '<%=translate("Preparing download...")%>';
|
||||
urlText.textContent = '';
|
||||
|
||||
// 切换按钮为取消下载
|
||||
confirmBtn.textContent = '<%=translate("Cancel Download")%>';
|
||||
confirmBtn.onclick = function() { cancelDownload(); };
|
||||
|
||||
// 模拟进度条:缓慢随机增长到70-80%
|
||||
var fakeProgress = 0;
|
||||
var fakeTimer = setInterval(function() {
|
||||
if (fakeProgress < 70) {
|
||||
fakeProgress += Math.random() * 3; // 每次增加0-3%
|
||||
if (fakeProgress > 70) fakeProgress = 70;
|
||||
progressFill.style.width = fakeProgress + '%';
|
||||
progressText.textContent = '<%=translate("Downloading...")%> ' + Math.floor(fakeProgress) + '%';
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// 发送下载请求
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '<%=url("admin", "vpn", "easytier", "download_easytier")%>', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
|
||||
xhr.onload = function() {
|
||||
clearInterval(fakeTimer);
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
// 成功:直接跳到100%
|
||||
progressFill.style.width = '100%';
|
||||
progressText.textContent = '<%=translate("Download completed successfully")%>';
|
||||
|
||||
// 恢复按钮
|
||||
confirmBtn.textContent = '<%=translate("Download")%>';
|
||||
confirmBtn.onclick = function() { startDownload(); };
|
||||
|
||||
setTimeout(function() {
|
||||
closeDownloadModal();
|
||||
showAlert('success', '<%=translate("Download and installation completed")%>');
|
||||
// 显示重启确认模态窗口
|
||||
showRestartModal();
|
||||
}, 1000);
|
||||
} else {
|
||||
// 失败:显示错误
|
||||
var errorMsg = response.message || '<%=translate("Download failed")%>';
|
||||
progressText.textContent = errorMsg;
|
||||
progressFill.style.backgroundColor = 'var(--error)';
|
||||
showAlert('error', errorMsg);
|
||||
// 按钮变为重试
|
||||
confirmBtn.textContent = '<%=translate("Retry")%>';
|
||||
confirmBtn.onclick = function() {
|
||||
// 重置进度条
|
||||
progressFill.style.width = '0%';
|
||||
progressFill.style.backgroundColor = '';
|
||||
progress.style.display = 'none';
|
||||
// 恢复按钮
|
||||
confirmBtn.textContent = '<%=translate("Download")%>';
|
||||
confirmBtn.onclick = function() { startDownload(); };
|
||||
// 重新开始下载
|
||||
startDownload();
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
progressText.textContent = '<%=translate("Download failed")%>';
|
||||
progressFill.style.backgroundColor = 'var(--error)';
|
||||
showAlert('error', '<%=translate("Download failed")%>');
|
||||
// 恢复按钮
|
||||
confirmBtn.textContent = '<%=translate("Download")%>';
|
||||
confirmBtn.onclick = function() { startDownload(); };
|
||||
}
|
||||
} else {
|
||||
progressText.textContent = '<%=translate("Download failed")%>';
|
||||
progressFill.style.backgroundColor = 'var(--error)';
|
||||
showAlert('error', '<%=translate("Download failed")%>');
|
||||
// 恢复按钮
|
||||
confirmBtn.textContent = '<%=translate("Download")%>';
|
||||
confirmBtn.onclick = function() { startDownload(); };
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
clearInterval(fakeTimer);
|
||||
progressText.textContent = '<%=translate("Download failed")%>';
|
||||
progressFill.style.backgroundColor = 'var(--error)';
|
||||
showAlert('error', '<%=translate("Network error")%>');
|
||||
// 恢复按钮
|
||||
confirmBtn.textContent = '<%=translate("Download")%>';
|
||||
confirmBtn.onclick = function() { startDownload(); };
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify({
|
||||
version: version
|
||||
}));
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 先初始化版本管理器(开始获取版本列表)
|
||||
versionManager.init();
|
||||
// 然后加载配置(会调用versionManager.setVersion)
|
||||
loadConfig();
|
||||
});
|
||||
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
|
||||
@ -487,7 +487,7 @@ input:disabled + .slider {
|
||||
<option value="trace"><%:Trace%></option>
|
||||
</select>
|
||||
<span class="description"><%:Runtime log located at%> /tmp/easytierweb.log, <%:viewable in the log section%>.<br>
|
||||
<%:Log levels%>: Error < Warning < Info < Debug < Trace</span>
|
||||
<%:Log levels%>: <%:Error%> < <%:Warning%> < <%:Info%> < <%:Debug%> < <%:Trace%></span>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
|
||||
@ -1851,3 +1851,131 @@ msgstr "目标实例ID"
|
||||
msgid "Logs"
|
||||
msgstr "日志"
|
||||
|
||||
msgid "Online Download"
|
||||
msgstr "在线下载"
|
||||
|
||||
msgid "Online Download EasyTier"
|
||||
msgstr "在线下载EasyTier程序"
|
||||
|
||||
msgid "Download Version"
|
||||
msgstr "下载版本"
|
||||
|
||||
msgid "Download specified version from GitHub and install to specified path"
|
||||
msgstr "从GitHub下载指定版本的程序安装到指定路径"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "正在下载..."
|
||||
|
||||
msgid "Preparing download..."
|
||||
msgstr "准备下载..."
|
||||
|
||||
msgid "Download completed successfully"
|
||||
msgstr "下载完成"
|
||||
|
||||
msgid "Download failed"
|
||||
msgstr "下载失败"
|
||||
|
||||
msgid "Download and installation completed"
|
||||
msgstr "下载安装完成"
|
||||
|
||||
msgid "Download completed. Restart service now?"
|
||||
msgstr "下载完成,是否立即重启服务?"
|
||||
|
||||
msgid "Please enter download version"
|
||||
msgstr "请输入下载版本"
|
||||
|
||||
msgid "Network error"
|
||||
msgstr "网络错误"
|
||||
|
||||
msgid "Pre-release"
|
||||
msgstr "预发布版"
|
||||
|
||||
msgid "Stable"
|
||||
msgstr "稳定版"
|
||||
|
||||
msgid "Custom Input"
|
||||
msgstr "自定义输入"
|
||||
|
||||
msgid "Loading versions..."
|
||||
msgstr "正在加载版本..."
|
||||
|
||||
msgid "Program not found"
|
||||
msgstr "程序不存在"
|
||||
|
||||
msgid "no running instances found"
|
||||
msgstr "未找到正在运行的实例"
|
||||
|
||||
msgid "Unable to detect CPU architecture"
|
||||
msgstr "无法检测CPU架构"
|
||||
|
||||
msgid "No x86-32bit program available"
|
||||
msgstr "没有当前x86-32位的程序"
|
||||
|
||||
msgid "System lacks unzip, cannot download and extract"
|
||||
msgstr "系统缺少 unzip 下载后无法解压"
|
||||
|
||||
msgid "Missing version parameter"
|
||||
msgstr "缺少版本号参数"
|
||||
|
||||
msgid "Starting download..."
|
||||
msgstr "开始下载..."
|
||||
|
||||
msgid "Trying download URL..."
|
||||
msgstr "尝试下载地址..."
|
||||
|
||||
msgid "Downloading with"
|
||||
msgstr "使用"
|
||||
|
||||
msgid "Download completed, extracting..."
|
||||
msgstr "下载完成,正在解压..."
|
||||
|
||||
msgid "Extraction failed"
|
||||
msgstr "解压失败"
|
||||
|
||||
msgid "Installing programs..."
|
||||
msgstr "正在安装程序..."
|
||||
|
||||
msgid "Installation completed"
|
||||
msgstr "安装完成"
|
||||
|
||||
msgid "Preparing..."
|
||||
msgstr "准备中..."
|
||||
|
||||
msgid "Downloaded package is incomplete, missing required files"
|
||||
msgstr "下载的压缩包不完整,缺少EasyTier相关程序文件"
|
||||
|
||||
msgid "Program installation failed, verification failed"
|
||||
msgstr "程序安装失败,验证未通过"
|
||||
|
||||
msgid "Download task already exists"
|
||||
msgstr "已有下载任务正在进行"
|
||||
|
||||
msgid "Verifying programs..."
|
||||
msgstr "正在验证程序..."
|
||||
|
||||
msgid "Program is incomplete or architecture mismatch"
|
||||
msgstr "程序不完整或架构不匹配"
|
||||
|
||||
msgid "Failed to install program"
|
||||
msgstr "程序安装失败"
|
||||
|
||||
msgid "Cancel Download"
|
||||
msgstr "取消下载"
|
||||
|
||||
msgid "Download cancelled"
|
||||
msgstr "已取消下载"
|
||||
|
||||
msgid "All download attempts failed"
|
||||
msgstr "所有下载地址均下载失败,请确认github加速镜像地址有效或使用手动上传"
|
||||
|
||||
msgid "Download task already exists, continuing..."
|
||||
msgstr "下载任务已存在,继续中..."
|
||||
|
||||
msgid "Download Successful"
|
||||
msgstr "下载成功"
|
||||
|
||||
msgid "Retry"
|
||||
msgstr "重试"
|
||||
|
||||
msgid "Close"
|
||||
msgstr "关闭"
|
||||
|
||||
@ -103,7 +103,7 @@ check_bin() {
|
||||
# 如果获取失败,从 UCI 配置或使用默认版本
|
||||
if [ -z "$tag" ]; then
|
||||
tag=$(uci -q get easytier.@easytier[0].fallback_version)
|
||||
[ -z "$tag" ] && tag="v2.5.0"
|
||||
[ -z "$tag" ] && tag="v2.6.0"
|
||||
fi
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始在线下载${tag}版本,${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip下载较慢耐心等候" >>/tmp/easytier.log
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始在线下载${tag}版本,${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip下载较慢耐心等候" >>/tmp/easytierweb.log
|
||||
|
||||
@ -42,7 +42,7 @@ get_latest_version() {
|
||||
# 如果获取失败,从 UCI 配置或使用默认版本
|
||||
if [ -z "$tag" ]; then
|
||||
tag=$(uci -q get easytier.@easytier[0].fallback_version)
|
||||
[ -z "$tag" ] && tag="v2.5.0"
|
||||
[ -z "$tag" ] && tag="v2.6.0"
|
||||
fi
|
||||
|
||||
echo "$tag"
|
||||
|
||||
@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.4.15
|
||||
PKG_RELEASE:=76
|
||||
PKG_RELEASE:=77
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.4.10
|
||||
PKG_RELEASE:=32
|
||||
PKG_RELEASE:=33
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
Loading…
Reference in New Issue
Block a user