🚀 Sync 2026-04-17 20:33:33

This commit is contained in:
github-actions[bot] 2026-04-17 20:33:33 +08:00
parent 959aa5c65a
commit f786b4b976
13 changed files with 1527 additions and 69 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>&nbsp;&nbsp;"
.. "<a href=\"http://easytier.cn\" target=\"_blank\">Official Documentation</a>&nbsp;&nbsp;"
.. "<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>&nbsp;&nbsp;")
.. "<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>&nbsp;"
.. "<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>&nbsp;"
.. "<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
-- 状态卡片

View File

@ -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();
}
// 自动刷新

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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 "关闭"

View File

@ -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

View File

@ -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"

View File

@ -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:= \

View File

@ -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:= \