diff --git a/luci-app-easytier/easytier-noweb/Makefile b/luci-app-easytier/easytier-noweb/Makefile
index d383ad3e..5f3ed3cd 100644
--- a/luci-app-easytier/easytier-noweb/Makefile
+++ b/luci-app-easytier/easytier-noweb/Makefile
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
PKG_NAME:=easytier-noweb
-PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.2)
+PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.4)
ifeq ($(ARCH),mipsel)
APP_ARCH:=mipsel
@@ -36,6 +36,8 @@ PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
+RSTRIP:=:
+
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
@@ -63,8 +65,10 @@ endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin
- $(INSTALL_BIN) $(PKG_BUILD_DIR)/easytier-core $(1)/usr/bin/easytier-core
- $(INSTALL_BIN) $(PKG_BUILD_DIR)/easytier-cli $(1)/usr/bin/easytier-cli
+ $(CP) $(PKG_BUILD_DIR)/easytier-core $(1)/usr/bin/
+ chmod 0755 $(1)/usr/bin/easytier-core
+ $(CP) $(PKG_BUILD_DIR)/easytier-cli $(1)/usr/bin/
+ chmod 0755 $(1)/usr/bin/easytier-cli
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
diff --git a/luci-app-easytier/easytier/Makefile b/luci-app-easytier/easytier/Makefile
index 66b0f4ca..8a2f4a33 100644
--- a/luci-app-easytier/easytier/Makefile
+++ b/luci-app-easytier/easytier/Makefile
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
PKG_NAME:=easytier
-PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.2)
+PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.4)
ifeq ($(ARCH),mipsel)
APP_ARCH:=mipsel
diff --git a/luci-app-easytier/luci-app-easytier/Makefile b/luci-app-easytier/luci-app-easytier/Makefile
index da66bb52..dc487484 100644
--- a/luci-app-easytier/luci-app-easytier/Makefile
+++ b/luci-app-easytier/luci-app-easytier/Makefile
@@ -7,7 +7,7 @@
include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
-PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.2)
+PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.4)
PKG_RELEASE:=1
LUCI_TITLE:=LuCI support for EasyTier
diff --git a/luci-app-easytier/luci-app-easytier/luasrc/controller/easytier.lua b/luci-app-easytier/luci-app-easytier/luasrc/controller/easytier.lua
index 96a6c164..395c67a4 100644
--- a/luci-app-easytier/luci-app-easytier/luasrc/controller/easytier.lua
+++ b/luci-app-easytier/luci-app-easytier/luasrc/controller/easytier.lua
@@ -163,7 +163,7 @@ function get_upload_config()
easytierbin = uci:get_first("easytier", "easytier", "easytierbin") or "/usr/bin/easytier-core",
webbin = uci:get_first("easytier", "easytier", "webbin") or "/usr/bin/easytier-web",
github_proxys = {},
- fallback_version = uci:get_first("easytier", "easytier", "fallback_version") or "v2.6.2"
+ fallback_version = uci:get_first("easytier", "easytier", "fallback_version") or "v2.6.4"
}
-- 读取代理列表(list 类型)
@@ -200,7 +200,7 @@ function save_upload_config()
-- 保存配置
uci:set("easytier", "@easytier[0]", "easytierbin", data.easytierbin or "/usr/bin/easytier-core")
uci:set("easytier", "@easytier[0]", "webbin", data.webbin or "/usr/bin/easytier-web")
- uci:set("easytier", "@easytier[0]", "fallback_version", data.fallback_version or "v2.6.2")
+ uci:set("easytier", "@easytier[0]", "fallback_version", data.fallback_version or "v2.6.4")
-- 删除旧的代理列表
uci:delete("easytier", "@easytier[0]", "github_proxys")
diff --git a/luci-app-easytier/luci-app-easytier/luasrc/model/cbi/easytier.lua b/luci-app-easytier/luci-app-easytier/luasrc/model/cbi/easytier.lua
index ec83091c..4476fb1d 100644
--- a/luci-app-easytier/luci-app-easytier/luasrc/model/cbi/easytier.lua
+++ b/luci-app-easytier/luci-app-easytier/luasrc/model/cbi/easytier.lua
@@ -56,12 +56,6 @@ network_name = s:taboption("general", Value, "network_name", translate("Network
network_name.password = true
network_name.placeholder = "easytier-name"
network_name.maxlength = 64
-network_name.validate = function(self, value)
- if value and value ~= "" and value:match("[^%w%-_]") then
- return nil, translate("Only alphanumeric characters, hyphens and underscores allowed")
- end
- return value
-end
network_name:depends("etcmd", "etcmd")
network_secret = s:taboption("general", Value, "network_secret", translate("Network Secret"),
@@ -412,9 +406,28 @@ foreign_relay_bps_limit = s:taboption("privacy", Value, "foreign_relay_bps_limit
.. "(--foreign-relay-bps-limit parameter)"))
foreign_relay_bps_limit:depends("etcmd", "etcmd")
-extra_args = s:taboption("privacy", Value, "extra_args", translate("Extra Parameters"),
+extra_args = s:taboption("privacy", DynamicList, "extra_args", translate("Extra Parameters"),
translate("Additional command-line arguments passed to the backend process"))
-extra_args.placeholder = "--tcp-whitelist 80 --udp-whitelist 53"
+extra_args.placeholder = "--tcp-whitelist=80"
+extra_args.validate = function(self, value, section)
+ if type(value) == "table" then
+ local seen = {}
+ local i
+ for i = 1, #value do
+ local arg = value[i]
+ if arg then
+ arg = tostring(arg):match("^%s*(.-)%s*$")
+ if arg ~= "" then
+ if seen[arg] then
+ return nil, translate("Duplicate extra parameter") .. ": " .. arg
+ end
+ seen[arg] = true
+ end
+ end
+ end
+ end
+ return value
+end
extra_args:depends("etcmd", "etcmd")
log = s:taboption("general", ListValue, "log", translate("Program Log"),
diff --git a/luci-app-easytier/luci-app-easytier/luasrc/view/easytier/easytier_cli.htm b/luci-app-easytier/luci-app-easytier/luasrc/view/easytier/easytier_cli.htm
index 0dc2dca5..38b71400 100644
--- a/luci-app-easytier/luci-app-easytier/luasrc/view/easytier/easytier_cli.htm
+++ b/luci-app-easytier/luci-app-easytier/luasrc/view/easytier/easytier_cli.htm
@@ -370,6 +370,8 @@ local uci = require 'luci.model.uci'.cursor()
border-radius: 12px;
white-space: pre-wrap;
word-wrap: break-word;
+ overflow-wrap: anywhere;
+ word-break: break-word;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
color: var(--text-primary);
@@ -377,6 +379,34 @@ local uci = require 'luci.model.uci'.cursor()
line-height: 1.5;
}
+.et-command-panel {
+ position: relative;
+}
+
+.et-command-toolbar {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 8px;
+}
+
+.et-copy-btn {
+ display: inline-flex;
+ align-items: center;
+ padding: 5px 10px;
+ background: var(--tab-active);
+ color: #fff;
+ border: 1px solid var(--tab-active);
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1.4;
+}
+
+.et-copy-btn:hover {
+ opacity: 0.9;
+}
+
@media (max-width: 768px) {
.et-conninfo-header {
flex-direction: column;
@@ -893,6 +923,50 @@ function escapeHtml(text) {
return div.innerHTML;
}
+function copyTextFromElement(elementId, button) {
+ var el = document.getElementById(elementId);
+ if (!el) return;
+ var text = el.textContent || el.innerText || '';
+
+ function markCopied() {
+ if (!button) return;
+ var oldText = button.textContent;
+ button.textContent = '<%:Copied%>';
+ window.setTimeout(function() {
+ button.textContent = oldText;
+ }, 1500);
+ }
+
+ if (navigator.clipboard && window.isSecureContext) {
+ navigator.clipboard.writeText(text).then(markCopied);
+ return;
+ }
+
+ var textarea = document.createElement('textarea');
+ textarea.value = text;
+ textarea.setAttribute('readonly', 'readonly');
+ textarea.style.position = 'fixed';
+ textarea.style.left = '-9999px';
+ document.body.appendChild(textarea);
+ textarea.select();
+ try {
+ document.execCommand('copy');
+ markCopied();
+ } finally {
+ document.body.removeChild(textarea);
+ }
+}
+
+function renderCopyablePre(content, className) {
+ var id = 'et_copy_' + Math.random().toString(36).slice(2);
+ return '
' +
+ '
' +
+ '' +
+ '
' +
+ '
' + escapeHtml(content) + '
' +
+ '
';
+}
+
function parseTable(content, tabName) {
if (!content || content.trim() === '') {
return '<%:No data available%>
';
@@ -916,6 +990,47 @@ function parseTable(content, tabName) {
if (content.indexOf('Error: Invalid service name: "PeerCenterRpc"') !== -1) {
return '<%:Peer Center service not available. This feature requires multiple nodes in the network.%>
';
}
+
+ // 启动参数可能包含管道符等特殊字符,必须先于表格解析处理。
+ if (tabName === 'cmdline') {
+ var html = renderCopyablePre(content);
+
+ // 检查是否使用配置文件启动
+ if (content.indexOf('--config-file') !== -1 || content.match(/-c\s+\//)) {
+ XHR.get('<%=url("admin/vpn/easytier/api_conninfo")%>', null, function(x, data) {
+ if (data && data.config_file) {
+ var configDiv = document.createElement('div');
+ configDiv.className = 'et-pre';
+ configDiv.style.marginTop = '16px';
+ configDiv.style.borderTop = '2px solid var(--border-color)';
+ configDiv.style.paddingTop = '16px';
+
+ var title = document.createElement('div');
+ title.style.fontWeight = '600';
+ title.style.marginBottom = '8px';
+ title.style.color = 'var(--primary-color)';
+ title.textContent = tr('Configuration File Content') + ':';
+
+ var contentDiv = document.createElement('pre');
+ contentDiv.style.margin = '0';
+ contentDiv.style.whiteSpace = 'pre-wrap';
+ contentDiv.style.overflowWrap = 'anywhere';
+ contentDiv.style.wordBreak = 'break-word';
+ contentDiv.textContent = data.config_file;
+
+ configDiv.appendChild(title);
+ configDiv.appendChild(contentDiv);
+
+ var container = document.getElementById('tab_cmdline');
+ if (container) {
+ container.appendChild(configDiv);
+ }
+ }
+ });
+ }
+
+ return html;
+ }
// 连接失败错误处理
if (content.indexOf('failed to get peer manager client') !== -1 ||
@@ -948,46 +1063,6 @@ function parseTable(content, tabName) {
var lines = content.split(/\r?\n/).filter(function(l) { return l.trim() !== ''; });
if (lines.length === 0 || lines[0].indexOf('|') === -1) {
- // 启动参数不翻译
- if (tabName === 'cmdline') {
- var html = '' + escapeHtml(content) + '
';
-
- // 检查是否使用配置文件启动
- if (content.indexOf('--config-file') !== -1 || content.match(/-c\s+\//)) {
- XHR.get('<%=url("admin/vpn/easytier/api_conninfo")%>', null, function(x, data) {
- if (data && data.config_file) {
- var configDiv = document.createElement('div');
- configDiv.className = 'et-pre';
- configDiv.style.marginTop = '16px';
- configDiv.style.borderTop = '2px solid var(--border-color)';
- configDiv.style.paddingTop = '16px';
-
- var title = document.createElement('div');
- title.style.fontWeight = '600';
- title.style.marginBottom = '8px';
- title.style.color = 'var(--primary-color)';
- title.textContent = tr('Configuration File Content') + ':';
-
- var contentDiv = document.createElement('pre');
- contentDiv.style.margin = '0';
- contentDiv.style.whiteSpace = 'pre-wrap';
- contentDiv.style.wordBreak = 'break-all';
- contentDiv.textContent = data.config_file;
-
- configDiv.appendChild(title);
- configDiv.appendChild(contentDiv);
-
- var container = document.querySelector('.et-tab-content[data-tab="cmdline"]');
- if (container) {
- container.appendChild(configDiv);
- }
- }
- });
- }
-
- return html;
- }
-
// 对非表格内容进行全面翻译处理
var translatedContent = content;
diff --git a/luci-app-easytier/luci-app-easytier/luasrc/view/easytier/easytier_upload.htm b/luci-app-easytier/luci-app-easytier/luasrc/view/easytier/easytier_upload.htm
index 209dd5ed..eb013744 100644
--- a/luci-app-easytier/luci-app-easytier/luasrc/view/easytier/easytier_upload.htm
+++ b/luci-app-easytier/luci-app-easytier/luasrc/view/easytier/easytier_upload.htm
@@ -383,7 +383,7 @@ local translate = i18n.translate
-
+