From f786b4b976ad539650761d89bd4fc8d68e35e3f6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 17 Apr 2026 20:33:33 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Sync=202026-04-17=2020:33:33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- daed/Makefile | 6 +- luci-app-easytier/Makefile | 2 +- .../luasrc/controller/easytier.lua | 389 ++++++++++ .../luasrc/model/cbi/easytier_status.lua | 3 + .../luasrc/view/easytier/easytier_cli.htm | 365 +++++++-- .../luasrc/view/easytier/easytier_log.htm | 3 + .../luasrc/view/easytier/easytier_upload.htm | 690 +++++++++++++++++- .../luasrc/view/easytier/easytier_web.htm | 2 +- luci-app-easytier/po/zh_Hans/easytier.po | 128 ++++ luci-app-easytier/root/etc/init.d/easytier | 2 +- .../root/usr/share/easytier/download.sh | 2 +- luci-app-passwall/Makefile | 2 +- luci-app-passwall2/Makefile | 2 +- 13 files changed, 1527 insertions(+), 69 deletions(-) diff --git a/daed/Makefile b/daed/Makefile index ca69e610..86f45863 100644 --- a/daed/Makefile +++ b/daed/Makefile @@ -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 diff --git a/luci-app-easytier/Makefile b/luci-app-easytier/Makefile index f80881fd..900f72df 100644 --- a/luci-app-easytier/Makefile +++ b/luci-app-easytier/Makefile @@ -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 diff --git a/luci-app-easytier/luasrc/controller/easytier.lua b/luci-app-easytier/luasrc/controller/easytier.lua index 8d12aa55..28c8f00c 100644 --- a/luci-app-easytier/luasrc/controller/easytier.lua +++ b/luci-app-easytier/luasrc/controller/easytier.lua @@ -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 diff --git a/luci-app-easytier/luasrc/model/cbi/easytier_status.lua b/luci-app-easytier/luasrc/model/cbi/easytier_status.lua index 31f22579..abf20e48 100644 --- a/luci-app-easytier/luasrc/model/cbi/easytier_status.lua +++ b/luci-app-easytier/luasrc/model/cbi/easytier_status.lua @@ -5,6 +5,9 @@ m.description = translate("A simple, secure, decentralized VPN solution for intr .. "Project URL: github.com/EasyTier/EasyTier  " .. "Official Documentation  " .. "QQ Group  ") + .. "\"\" " + .. "\"\" " + .. "\"\"" m.pageaction = false -- 状态卡片 diff --git a/luci-app-easytier/luasrc/view/easytier/easytier_cli.htm b/luci-app-easytier/luasrc/view/easytier/easytier_cli.htm index 2436db1a..0dc2dca5 100644 --- a/luci-app-easytier/luasrc/view/easytier/easytier_cli.htm +++ b/luci-app-easytier/luasrc/view/easytier/easytier_cli.htm @@ -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...%> - - - + + + @@ -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 '
' + escapeHtml(tr('ERROR: Wireguard VPN Portal Not Started')) + '
'; } + // 未找到运行实例错误处理 + if (content.indexOf('no running instances found') !== -1) { + return '
' + escapeHtml(tr('no running instances found')) + '
'; + } + // Peer Center错误处理 if (content.indexOf('Error: Invalid service name: "PeerCenterRpc"') !== -1) { return '
<%:Peer Center service not available. This feature requires multiple nodes in the network.%>
'; @@ -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(); } // 自动刷新 diff --git a/luci-app-easytier/luasrc/view/easytier/easytier_log.htm b/luci-app-easytier/luasrc/view/easytier/easytier_log.htm index 0751b67b..5e6761ad 100644 --- a/luci-app-easytier/luasrc/view/easytier/easytier_log.htm +++ b/luci-app-easytier/luasrc/view/easytier/easytier_log.htm @@ -293,6 +293,9 @@ local translate = i18n.translate
+ + Ask DeepWiki + +
+
+ + +
+ +
<%=translate("Used when unable to fetch the latest version during online download")%>
@@ -408,6 +424,39 @@ local translate = i18n.translate
+ + +
<%=translate("Supported Files")%>
    @@ -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 += 'easytier-core: <%=translate("Program not found")%>'; } if (response.cli_size) { - sizeHtml += (sizeHtml ? ' | ' : '') + ' easytier-cli ' + response.cli_size; + sizeHtml += (sizeHtml ? ' | ' : '') + 'easytier-cli ' + response.cli_size; + } else { + sizeHtml += (sizeHtml ? ' | ' : '') + 'easytier-cli: <%=translate("Program not found")%>'; } } else if (inputId === 'webbin') { if (response.web_size) { sizeHtml = 'easytier-web ' + response.web_size; + } else { + sizeHtml = 'easytier-web: <%=translate("Program not found")%>'; } } sizeDiv.innerHTML = sizeHtml ? '<%=translate("Program size")%>: ' + sizeHtml + '' : ''; @@ -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(); +}); + //]]> diff --git a/luci-app-easytier/luasrc/view/easytier/easytier_web.htm b/luci-app-easytier/luasrc/view/easytier/easytier_web.htm index 69ce4ee3..0e013a9b 100644 --- a/luci-app-easytier/luasrc/view/easytier/easytier_web.htm +++ b/luci-app-easytier/luasrc/view/easytier/easytier_web.htm @@ -487,7 +487,7 @@ input:disabled + .slider { <%:Runtime log located at%> /tmp/easytierweb.log, <%:viewable in the log section%>.
    - <%:Log levels%>: Error < Warning < Info < Debug < Trace
    + <%:Log levels%>: <%:Error%> < <%:Warning%> < <%:Info%> < <%:Debug%> < <%:Trace%>
diff --git a/luci-app-easytier/po/zh_Hans/easytier.po b/luci-app-easytier/po/zh_Hans/easytier.po index df8bae63..0d2301f0 100644 --- a/luci-app-easytier/po/zh_Hans/easytier.po +++ b/luci-app-easytier/po/zh_Hans/easytier.po @@ -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 "关闭" diff --git a/luci-app-easytier/root/etc/init.d/easytier b/luci-app-easytier/root/etc/init.d/easytier index a9df6c35..da293ff1 100755 --- a/luci-app-easytier/root/etc/init.d/easytier +++ b/luci-app-easytier/root/etc/init.d/easytier @@ -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 diff --git a/luci-app-easytier/root/usr/share/easytier/download.sh b/luci-app-easytier/root/usr/share/easytier/download.sh index 083253d8..7c97e28e 100755 --- a/luci-app-easytier/root/usr/share/easytier/download.sh +++ b/luci-app-easytier/root/usr/share/easytier/download.sh @@ -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" diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index f9adccb3..4526db95 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -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:= \ diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 226098e9..28a3684f 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -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:= \