mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-14 04:15:06 +08:00
Compare commits
2
Commits
d9c423b124
...
0ee799dbde
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ee799dbde | ||
|
|
91e0df6c86 |
+1
-1
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=frp
|
PKG_NAME:=frp
|
||||||
PKG_VERSION:=0.71.0
|
PKG_VERSION:=0.71.0
|
||||||
PKG_RELEASE:=9
|
PKG_RELEASE:=10
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/fatedier/frp/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/fatedier/frp/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ config init
|
|||||||
config conf 'common'
|
config conf 'common'
|
||||||
option bind_addr '0.0.0.0'
|
option bind_addr '0.0.0.0'
|
||||||
option bind_port '7000'
|
option bind_port '7000'
|
||||||
|
|
||||||
option authentication_method 'token'
|
option authentication_method 'token'
|
||||||
# option token 'your_token'
|
# option token 'your_token'
|
||||||
# Alternatively, load a token from a file or command. Exec token sources
|
# Alternatively, load a token from a file or command. Exec token sources
|
||||||
@@ -32,12 +31,10 @@ config conf 'common'
|
|||||||
# list token_source_exec_args '--format'
|
# list token_source_exec_args '--format'
|
||||||
# list token_source_exec_args 'raw'
|
# list token_source_exec_args 'raw'
|
||||||
# list token_source_exec_env 'TOKEN_SERVICE=production'
|
# list token_source_exec_env 'TOKEN_SERVICE=production'
|
||||||
|
|
||||||
option max_pool_count '5'
|
option max_pool_count '5'
|
||||||
option tcp_mux 'true'
|
option tcp_mux 'true'
|
||||||
option tls_force 'false'
|
option tls_force 'false'
|
||||||
option detailed_errors_to_client 'true'
|
option detailed_errors_to_client 'true'
|
||||||
|
|
||||||
# Web server is disabled when admin_port is empty or 0.
|
# Web server is disabled when admin_port is empty or 0.
|
||||||
# option admin_addr '127.0.0.1'
|
# option admin_addr '127.0.0.1'
|
||||||
# option admin_port '7500'
|
# option admin_port '7500'
|
||||||
@@ -48,15 +45,12 @@ config conf 'common'
|
|||||||
option enable_prometheus 'false'
|
option enable_prometheus 'false'
|
||||||
# option admin_tls_cert_file '/etc/ssl/acme/example.com.fullchain.crt'
|
# option admin_tls_cert_file '/etc/ssl/acme/example.com.fullchain.crt'
|
||||||
# option admin_tls_key_file '/etc/ssl/acme/example.com.key'
|
# option admin_tls_key_file '/etc/ssl/acme/example.com.key'
|
||||||
|
|
||||||
# Allow ports can be single ports or ranges.
|
# Allow ports can be single ports or ranges.
|
||||||
# list allow_ports '2000-3000'
|
# list allow_ports '2000-3000'
|
||||||
# list allow_ports '3001'
|
# list allow_ports '3001'
|
||||||
|
|
||||||
option log_file 'console'
|
option log_file 'console'
|
||||||
option log_level 'info'
|
option log_level 'info'
|
||||||
option log_max_days '3'
|
option log_max_days '3'
|
||||||
|
|
||||||
# List options with name "_" will be directly appended as raw TOML lines.
|
# List options with name "_" will be directly appended as raw TOML lines.
|
||||||
# Use this only for options not covered by UCI options above.
|
# Use this only for options not covered by UCI options above.
|
||||||
# Do not duplicate keys generated by UCI options above.
|
# Do not duplicate keys generated by UCI options above.
|
||||||
@@ -69,7 +63,6 @@ config conf 'common'
|
|||||||
# option path '/handler'
|
# option path '/handler'
|
||||||
# list ops 'Login'
|
# list ops 'Login'
|
||||||
# option tls_verify 'false'
|
# option tls_verify 'false'
|
||||||
|
|
||||||
#config http_plugin 'port_manager'
|
#config http_plugin 'port_manager'
|
||||||
# option name 'port-manager'
|
# option name 'port-manager'
|
||||||
# option addr '127.0.0.1:9001'
|
# option addr '127.0.0.1:9001'
|
||||||
|
|||||||
+2
-2
@@ -9,8 +9,8 @@ include $(TOPDIR)/rules.mk
|
|||||||
PKG_ARCH_kaiplus:=$(ARCH)
|
PKG_ARCH_kaiplus:=$(ARCH)
|
||||||
|
|
||||||
PKG_NAME:=kaiplus
|
PKG_NAME:=kaiplus
|
||||||
PKG_VERSION:=1.0.7
|
PKG_VERSION:=1.0.8
|
||||||
PKG_RELEASE:=11
|
PKG_RELEASE:=12
|
||||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kaiplus-runtime-v$(PKG_VERSION)/
|
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kaiplus-runtime-v$(PKG_VERSION)/
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
|||||||
PKG_NAME:=linkease-common-bin
|
PKG_NAME:=linkease-common-bin
|
||||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||||
PKG_SOURCE_DATE:=1.7.5
|
PKG_SOURCE_DATE:=1.7.5
|
||||||
PKG_RELEASE:=29
|
PKG_RELEASE:=30
|
||||||
|
|
||||||
ARCH_HEXCODE:=
|
ARCH_HEXCODE:=
|
||||||
ifeq ($(ARCH),x86_64)
|
ifeq ($(ARCH),x86_64)
|
||||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
|||||||
ARCH_HEXCODE=1b0c
|
ARCH_HEXCODE=1b0c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PKG_SOURCE_VERSION:=af4346ed87096bc76261ca089d336e3724731806
|
PKG_SOURCE_VERSION:=07a6d4293d28946ee8c39d0604985613c0c50e2d
|
||||||
PKG_SOURCE:=linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
PKG_SOURCE:=linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
|||||||
PKG_NAME:=linkease
|
PKG_NAME:=linkease
|
||||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||||
PKG_SOURCE_DATE:=1.7.5
|
PKG_SOURCE_DATE:=1.7.5
|
||||||
PKG_RELEASE:=32
|
PKG_RELEASE:=33
|
||||||
|
|
||||||
ARCH_HEXCODE:=
|
ARCH_HEXCODE:=
|
||||||
ifeq ($(ARCH),x86_64)
|
ifeq ($(ARCH),x86_64)
|
||||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
|||||||
ARCH_HEXCODE=1b0c
|
ARCH_HEXCODE=1b0c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PKG_SOURCE_VERSION:=af4346ed87096bc76261ca089d336e3724731806
|
PKG_SOURCE_VERSION:=07a6d4293d28946ee8c39d0604985613c0c50e2d
|
||||||
PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
|||||||
PKG_NAME:=linkeasefull
|
PKG_NAME:=linkeasefull
|
||||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||||
PKG_SOURCE_DATE:=3.0.19
|
PKG_SOURCE_DATE:=3.0.19
|
||||||
PKG_RELEASE:=31
|
PKG_RELEASE:=32
|
||||||
|
|
||||||
ARCH_HEXCODE:=
|
ARCH_HEXCODE:=
|
||||||
ifeq ($(ARCH),x86_64)
|
ifeq ($(ARCH),x86_64)
|
||||||
@@ -24,7 +24,7 @@ LINKEASE_RUNTIME_ARCH:=arm64
|
|||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PKG_SOURCE_VERSION:=af4346ed87096bc76261ca089d336e3724731806
|
PKG_SOURCE_VERSION:=07a6d4293d28946ee8c39d0604985613c0c50e2d
|
||||||
PKG_SOURCE:=linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH).tar.gz
|
PKG_SOURCE:=linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH).tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkeasefull-runtime-v$(PKG_SOURCE_DATE)/
|
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkeasefull-runtime-v$(PKG_SOURCE_DATE)/
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH)
|
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH)
|
||||||
|
|||||||
@@ -530,7 +530,7 @@ const commonTransportConf = [
|
|||||||
const commonTlsQuicConf = [
|
const commonTlsQuicConf = [
|
||||||
[form.Flag, 'tls_force', _('Force TLS'),
|
[form.Flag, 'tls_force', _('Force TLS'),
|
||||||
_('Only accept TLS-encrypted frpc connections.'),
|
_('Only accept TLS-encrypted frpc connections.'),
|
||||||
{ datatype: 'bool', default: 'false' }],
|
{ datatype: 'bool', default: 'false', optional: false, rmempty: false }],
|
||||||
|
|
||||||
[form.Value, 'tls_cert_file', _('TLS certificate path'),
|
[form.Value, 'tls_cert_file', _('TLS certificate path'),
|
||||||
_('Path to the TLS certificate file.'),
|
_('Path to the TLS certificate file.'),
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
LUCI_TITLE:=KaiPlus
|
LUCI_TITLE:=KaiPlus
|
||||||
PKG_VERSION:=1.0.7
|
PKG_VERSION:=1.0.8
|
||||||
PKG_RELEASE:=8
|
PKG_RELEASE:=9
|
||||||
LUCI_DEPENDS:=+kaiplus +luci-compat +luci-lib-linkeaseauth
|
LUCI_DEPENDS:=+kaiplus +luci-compat +luci-lib-linkeaseauth
|
||||||
LUCI_MINIFY_CSS:=0
|
LUCI_MINIFY_CSS:=0
|
||||||
LUCI_MINIFY_JS:=0
|
LUCI_MINIFY_JS:=0
|
||||||
|
|||||||
@@ -4,6 +4,25 @@
|
|||||||
|
|
||||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
|
||||||
|
|
||||||
|
## [2.2.1] - 2026-09-10
|
||||||
|
|
||||||
|
本次发布为紧急热修版本,修复基本设置页面渲染崩溃、一键升级无效、设备配对异常处理缺陷,并改善 Web 控制台安全上下文与图片附件支持。
|
||||||
|
|
||||||
|
### 修复与优化
|
||||||
|
|
||||||
|
- **修复基本设置页面崩溃与升级失效 (#106)**:
|
||||||
|
- 修复 `basic.lua` 中未定义 `service_ctl_url` 变量导致页面白屏崩溃的缺陷;
|
||||||
|
- 绑定正确的请求端点,确保「一键升级 OpenClaw 核心」能够正常向后台发起升级事务请求。
|
||||||
|
- **修复设备配对错误处理与假成功语义 (#106)**:
|
||||||
|
- 控制器端严格校验 `devices list --json` 及 `devices approve` 的执行退出码与 JSON 解析结果,杜绝网关异常时伪装为 0 待配对成功;
|
||||||
|
- 前端界面区分语义:仅当实际批准设备数大于 0 时提示成功并重新载入控制台,无待配对时仅展示信息提示,杜绝死循环重新载入。
|
||||||
|
- **改善 Web 控制台安全上下文与图片附件支持 (#105)**:
|
||||||
|
- 识别现代浏览器安全上下文限制(图片/附件与语音需 HTTPS 或 localhost),新增「访问条件提示(图片/附件与安全上下文)」面板及使用指引;
|
||||||
|
- 支持配置反代 HTTPS 控制台地址(`option console_url`),并提供「新窗口直达」入口避免 Mixed Content 限制。
|
||||||
|
- **契约测试与健壮性加固**:
|
||||||
|
- 新增 `test_luci_basic_contract.sh`,在测试流水线中以 Lua 解释器真实执行 CBI 模型渲染,杜绝变量缺失;
|
||||||
|
- 强化设备配对测试与控制台接入测试断言。
|
||||||
|
|
||||||
## [2.2.0] - 2026-09-06
|
## [2.2.0] - 2026-09-06
|
||||||
|
|
||||||
本次发布全面适配 OpenClaw 2026.9.1 最新稳定版基线,完成安全升级事务、外部 Supervisor 治理、微信插件 2.4.8 与废弃特性清理。
|
本次发布全面适配 OpenClaw 2026.9.1 最新稳定版基线,完成安全升级事务、外部 Supervisor 治理、微信插件 2.4.8 与废弃特性清理。
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-openclaw
|
PKG_NAME:=luci-app-openclaw
|
||||||
PKG_VERSION:=$(strip $(shell cat $(CURDIR)/VERSION 2>/dev/null || echo "1.0.0"))
|
PKG_VERSION:=$(strip $(shell cat $(CURDIR)/VERSION 2>/dev/null || echo "1.0.0"))
|
||||||
PKG_RELEASE:=27
|
PKG_RELEASE:=28
|
||||||
|
|
||||||
PKG_MAINTAINER:=10000ge10000 <10000ge10000@users.noreply.github.com>
|
PKG_MAINTAINER:=10000ge10000 <10000ge10000@users.noreply.github.com>
|
||||||
PKG_LICENSE:=GPL-3.0
|
PKG_LICENSE:=GPL-3.0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.2.0
|
2.2.1
|
||||||
|
|||||||
@@ -2396,14 +2396,26 @@ function action_devices_list()
|
|||||||
|
|
||||||
local output, exit_code = run_openclaw_devices_cli("devices list --json")
|
local output, exit_code = run_openclaw_devices_cli("devices list --json")
|
||||||
http.prepare_content("application/json")
|
http.prepare_content("application/json")
|
||||||
if not output then
|
if not output or exit_code ~= 0 then
|
||||||
http.write_json({ status = "error", message = "执行失败", pending = {}, paired = {} })
|
local err_msg = "查询设备列表失败"
|
||||||
|
if output and output:gsub("^%s+", ""):gsub("%s+$", "") ~= "" then
|
||||||
|
err_msg = output:sub(1, 300)
|
||||||
|
elseif type(exit_code) == "string" and exit_code ~= "" then
|
||||||
|
err_msg = exit_code
|
||||||
|
end
|
||||||
|
http.write_json({
|
||||||
|
status = "error",
|
||||||
|
message = err_msg,
|
||||||
|
exit_code = type(exit_code) == "number" and exit_code or -1,
|
||||||
|
pending = {},
|
||||||
|
paired = {}
|
||||||
|
})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local json_str = output:match("({%s*\"pending\".*})") or output:match("({.*})")
|
local json_str = output:match("({%s*\"pending\".*})") or output:match("({.*})")
|
||||||
local data = json_str and json.parse(json_str) or nil
|
local data = json_str and json.parse(json_str) or nil
|
||||||
if data and (data.pending or data.paired) then
|
if type(data) == "table" and (data.pending ~= nil or data.paired ~= nil) then
|
||||||
http.write_json({
|
http.write_json({
|
||||||
status = "ok",
|
status = "ok",
|
||||||
pending = data.pending or {},
|
pending = data.pending or {},
|
||||||
@@ -2411,7 +2423,8 @@ function action_devices_list()
|
|||||||
})
|
})
|
||||||
else
|
else
|
||||||
http.write_json({
|
http.write_json({
|
||||||
status = "ok",
|
status = "error",
|
||||||
|
message = "解析设备列表失败",
|
||||||
pending = {},
|
pending = {},
|
||||||
paired = {},
|
paired = {},
|
||||||
raw = output:sub(1, 300)
|
raw = output:sub(1, 300)
|
||||||
@@ -2432,9 +2445,38 @@ function action_devices_approve()
|
|||||||
|
|
||||||
if approve_all then
|
if approve_all then
|
||||||
local list_out, list_code = run_openclaw_devices_cli("devices list --json")
|
local list_out, list_code = run_openclaw_devices_cli("devices list --json")
|
||||||
local json_str = list_out and (list_out:match("({%s*\"pending\".*})") or list_out:match("({.*})"))
|
if not list_out or list_code ~= 0 then
|
||||||
|
http.prepare_content("application/json")
|
||||||
|
local err_msg = "查询设备列表失败"
|
||||||
|
if list_out and list_out:gsub("^%s+", ""):gsub("%s+$", "") ~= "" then
|
||||||
|
err_msg = list_out:sub(1, 300)
|
||||||
|
elseif type(list_code) == "string" and list_code ~= "" then
|
||||||
|
err_msg = list_code
|
||||||
|
end
|
||||||
|
http.write_json({
|
||||||
|
status = "error",
|
||||||
|
message = err_msg,
|
||||||
|
exit_code = type(list_code) == "number" and list_code or -1,
|
||||||
|
success_count = 0,
|
||||||
|
fail_count = 0
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local json_str = list_out:match("({%s*\"pending\".*})") or list_out:match("({.*})")
|
||||||
local data = json_str and json.parse(json_str) or nil
|
local data = json_str and json.parse(json_str) or nil
|
||||||
local pending = (data and data.pending) or {}
|
if type(data) ~= "table" or data.pending == nil then
|
||||||
|
http.prepare_content("application/json")
|
||||||
|
http.write_json({
|
||||||
|
status = "error",
|
||||||
|
message = "解析设备列表失败",
|
||||||
|
success_count = 0,
|
||||||
|
fail_count = 0
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pending = data.pending or {}
|
||||||
|
|
||||||
if #pending == 0 then
|
if #pending == 0 then
|
||||||
http.prepare_content("application/json")
|
http.prepare_content("application/json")
|
||||||
@@ -2447,12 +2489,16 @@ function action_devices_approve()
|
|||||||
if rid and rid ~= "" and rid:match("^[a-zA-Z0-9%-_]+$") then
|
if rid and rid ~= "" and rid:match("^[a-zA-Z0-9%-_]+$") then
|
||||||
local out, exit_code = run_openclaw_devices_cli("devices approve " .. shellquote(rid))
|
local out, exit_code = run_openclaw_devices_cli("devices approve " .. shellquote(rid))
|
||||||
local is_ok = (exit_code == 0) and (out and not out:match("No pending device") and not out:match("Error") and not out:match("ERROR"))
|
local is_ok = (exit_code == 0) and (out and not out:match("No pending device") and not out:match("Error") and not out:match("ERROR"))
|
||||||
|
local err_detail = out
|
||||||
|
if (not err_detail or err_detail == "") and type(exit_code) == "string" then
|
||||||
|
err_detail = exit_code
|
||||||
|
end
|
||||||
if is_ok or (out and out:lower():match("approved")) then
|
if is_ok or (out and out:lower():match("approved")) then
|
||||||
success_count = success_count + 1
|
success_count = success_count + 1
|
||||||
table.insert(results, { requestId = rid, ok = true, message = out })
|
table.insert(results, { requestId = rid, ok = true, message = out })
|
||||||
else
|
else
|
||||||
fail_count = fail_count + 1
|
fail_count = fail_count + 1
|
||||||
table.insert(results, { requestId = rid, ok = false, message = out or "批准失败" })
|
table.insert(results, { requestId = rid, ok = false, message = err_detail or "批准失败" })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2464,12 +2510,16 @@ function action_devices_approve()
|
|||||||
end
|
end
|
||||||
local out, exit_code = run_openclaw_devices_cli("devices approve " .. shellquote(request_id))
|
local out, exit_code = run_openclaw_devices_cli("devices approve " .. shellquote(request_id))
|
||||||
local is_ok = (exit_code == 0) and (out and not out:match("No pending device") and not out:match("Error") and not out:match("ERROR"))
|
local is_ok = (exit_code == 0) and (out and not out:match("No pending device") and not out:match("Error") and not out:match("ERROR"))
|
||||||
|
local err_detail = out
|
||||||
|
if (not err_detail or err_detail == "") and type(exit_code) == "string" then
|
||||||
|
err_detail = exit_code
|
||||||
|
end
|
||||||
if is_ok or (out and out:lower():match("approved")) then
|
if is_ok or (out and out:lower():match("approved")) then
|
||||||
success_count = 1
|
success_count = 1
|
||||||
table.insert(results, { requestId = request_id, ok = true, message = out })
|
table.insert(results, { requestId = request_id, ok = true, message = out })
|
||||||
else
|
else
|
||||||
fail_count = 1
|
fail_count = 1
|
||||||
table.insert(results, { requestId = request_id, ok = false, message = out or "批准失败" })
|
table.insert(results, { requestId = request_id, ok = false, message = err_detail or "批准失败" })
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
http.prepare_content("application/json")
|
http.prepare_content("application/json")
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ act = s3:option(DummyValue, "_actions")
|
|||||||
act.rawhtml = true
|
act.rawhtml = true
|
||||||
act.cfgvalue = function(self, section)
|
act.cfgvalue = function(self, section)
|
||||||
local ctl_url = luci.dispatcher.build_url("admin", "services", "openclaw", "service_ctl")
|
local ctl_url = luci.dispatcher.build_url("admin", "services", "openclaw", "service_ctl")
|
||||||
|
local service_ctl_url = ctl_url
|
||||||
local log_url = luci.dispatcher.build_url("admin", "services", "openclaw", "setup_log")
|
local log_url = luci.dispatcher.build_url("admin", "services", "openclaw", "setup_log")
|
||||||
local check_url = luci.dispatcher.build_url("admin", "services", "openclaw", "check_update")
|
local check_url = luci.dispatcher.build_url("admin", "services", "openclaw", "check_update")
|
||||||
local uninstall_url = luci.dispatcher.build_url("admin", "services", "openclaw", "uninstall")
|
local uninstall_url = luci.dispatcher.build_url("admin", "services", "openclaw", "uninstall")
|
||||||
|
|||||||
@@ -158,7 +158,12 @@ local pty_token = uci:get("openclaw", "main", "pty_token") or ""
|
|||||||
(new XHR()).get(devicesListUrl, null, function(x) {
|
(new XHR()).get(devicesListUrl, null, function(x) {
|
||||||
try {
|
try {
|
||||||
var data = JSON.parse(x.responseText);
|
var data = JSON.parse(x.responseText);
|
||||||
var pending = (data && data.pending) || [];
|
if (!data || data.status !== 'ok') {
|
||||||
|
if (advPairingBadge) advPairingBadge.textContent = '!';
|
||||||
|
if (advPairingBanner) advPairingBanner.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pending = data.pending || [];
|
||||||
if (advPairingBadge) advPairingBadge.textContent = pending.length;
|
if (advPairingBadge) advPairingBadge.textContent = pending.length;
|
||||||
if (pending.length > 0) {
|
if (pending.length > 0) {
|
||||||
var html = '';
|
var html = '';
|
||||||
@@ -204,11 +209,19 @@ local pty_token = uci:get("openclaw", "main", "pty_token") or ""
|
|||||||
try {
|
try {
|
||||||
var res = JSON.parse(x.responseText);
|
var res = JSON.parse(x.responseText);
|
||||||
if (res.status === 'ok') {
|
if (res.status === 'ok') {
|
||||||
advPairingMsg.style.color = '#52c41a';
|
if ((res.success_count || 0) > 0) {
|
||||||
advPairingMsg.innerHTML = '✅ ' + (res.message || '批准成功!');
|
advPairingMsg.style.color = '#52c41a';
|
||||||
setTimeout(function() {
|
advPairingMsg.innerHTML = '✅ ' + (res.message || '批准成功!');
|
||||||
checkAdvDevices();
|
setTimeout(function() {
|
||||||
}, 1500);
|
checkAdvDevices();
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
advPairingMsg.style.color = '#faad14';
|
||||||
|
advPairingMsg.innerHTML = 'ℹ️ ' + (res.message || '当前没有待配对的设备请求');
|
||||||
|
setTimeout(function() {
|
||||||
|
checkAdvDevices();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
advPairingMsg.style.color = '#cf222e';
|
advPairingMsg.style.color = '#cf222e';
|
||||||
advPairingMsg.innerHTML = '❌ ' + (res.message || '批准失败');
|
advPairingMsg.innerHTML = '❌ ' + (res.message || '批准失败');
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
<%
|
<%
|
||||||
local uci = require "luci.model.uci".cursor()
|
local uci = require "luci.model.uci".cursor()
|
||||||
local port = uci:get("openclaw", "main", "port") or "18789"
|
local port = uci:get("openclaw", "main", "port") or "18789"
|
||||||
|
local raw_console_url = tostring(uci:get("openclaw", "main", "console_url") or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||||
|
local https_console_url = ""
|
||||||
|
if raw_console_url ~= "" and raw_console_url:match("^https://[a-zA-Z0-9%-%._~:/?#%[%]@!$&'()*+,;=]+$") then
|
||||||
|
https_console_url = raw_console_url:gsub("/+$", "")
|
||||||
|
end
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
@@ -128,8 +133,8 @@ local port = uci:get("openclaw", "main", "port") or "18789"
|
|||||||
<button type="button" id="oc-console-pairing-toggle-btn" class="btn-action" title="管理/批准设备配对请求">📱 设备配对 <span id="oc-console-pairing-nav-badge" style="display:none; background:#ff4d4f; color:#fff; padding:0 6px; border-radius:10px; font-size:11px; font-weight:600; margin-left:2px;">0</span></button>
|
<button type="button" id="oc-console-pairing-toggle-btn" class="btn-action" title="管理/批准设备配对请求">📱 设备配对 <span id="oc-console-pairing-nav-badge" style="display:none; background:#ff4d4f; color:#fff; padding:0 6px; border-radius:10px; font-size:11px; font-weight:600; margin-left:2px;">0</span></button>
|
||||||
<button type="button" id="oc-console-refresh-btn" class="btn-action" title="重新载入当前会话">🔄 刷新</button>
|
<button type="button" id="oc-console-refresh-btn" class="btn-action" title="重新载入当前会话">🔄 刷新</button>
|
||||||
<button type="button" id="oc-console-fullscreen-btn" class="btn-action" title="全屏查看">⛶ 全屏</button>
|
<button type="button" id="oc-console-fullscreen-btn" class="btn-action" title="全屏查看">⛶ 全屏</button>
|
||||||
<a id="oc-console-open-btn" class="btn-open" href="#" target="_blank" rel="noopener">
|
<a id="oc-console-open-btn" class="btn-open" href="#" target="_blank" rel="noopener noreferrer" title="新窗口打开">
|
||||||
↗ 新窗口打开
|
↗ 新窗口直达
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,7 +167,23 @@ local port = uci:get("openclaw", "main", "port") or "18789"
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="oc-console-https-hint" style="display:none; padding:10px 14px; margin-bottom:14px; background:#fffbe6; border:1px solid #ffe58f; border-radius:6px; font-size:13px; color:#d48806; line-height:1.6;">
|
<div id="oc-console-https-hint" style="display:none; padding:10px 14px; margin-bottom:14px; background:#fffbe6; border:1px solid #ffe58f; border-radius:6px; font-size:13px; color:#d48806; line-height:1.6;">
|
||||||
⚠️ <strong>浏览器安全提示</strong>:当前正在通过 HTTPS 访问 LuCI。由于 OpenClaw 网关为 HTTP 服务,浏览器可能会拦截跨协议内嵌页面(Mixed Content)。若下方界面显示空白,请点击右上角「<strong>↗ 新窗口打开</strong>」直接访问会话,或改用 HTTP 协议访问 LuCI。
|
⚠️ <strong>浏览器安全提示</strong>:当前正在通过 HTTPS 访问 LuCI。由于 OpenClaw 网关默认提供 HTTP 服务,浏览器可能会拦截跨协议内嵌页面(Mixed Content)。若下方界面显示空白,请配置 HTTPS 控制台地址,或点击右上角「<strong>↗ 新窗口直达</strong>」直接访问会话。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="oc-console-context-hint" style="display:none; padding:10px 14px; margin-bottom:14px; background:#e6f4ff; border:1px solid #91caff; border-left:4px solid #1677ff; border-radius:6px; font-size:13px; color:#0958d9; line-height:1.6;">
|
||||||
|
<div style="font-weight:600; margin-bottom:4px; display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px;">
|
||||||
|
<span>💡 访问条件提示(图片/附件与安全上下文)</span>
|
||||||
|
<a id="oc-console-direct-btn" href="#" target="_blank" rel="noopener noreferrer" class="btn-action" style="background:#fff; color:#0958d9; border-color:#91caff; font-weight:normal; text-decoration:none; display:inline-flex; align-items:center; gap:4px;">
|
||||||
|
↗ 新窗口直达控制台
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="oc-console-context-detail" style="font-size:12px; color:#333;">
|
||||||
|
当前运行于非安全上下文(HTTP 局域网地址)。受现代浏览器安全策略(Secure Context)限制,图片粘贴、附件拖拽上传与麦克风/剪贴板权限可能受限。<br/>
|
||||||
|
<strong>如需使用完整附件/图片与语音功能,建议:</strong><br/>
|
||||||
|
1. <strong>HTTPS 控制台</strong>:配置反向代理并启用 HTTPS,在 UCI 中配置经过验证的 <code>option console_url 'https://...'</code>;<br/>
|
||||||
|
2. <strong>本地安全访问</strong>:在路由器本机或通过 SSH 端口转发访问 <code>http://localhost:18789/</code>;<br/>
|
||||||
|
3. <strong>新窗口直达</strong>:点击右上角或上方的「<strong>新窗口直达</strong>」在独立窗口运行。
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="oc-console-wrap" id="oc-console-wrap">
|
<div class="oc-console-wrap" id="oc-console-wrap">
|
||||||
@@ -178,6 +199,7 @@ local port = uci:get("openclaw", "main", "port") or "18789"
|
|||||||
//<![CDATA[
|
//<![CDATA[
|
||||||
(function() {
|
(function() {
|
||||||
var gwPort = '<%=port%>';
|
var gwPort = '<%=port%>';
|
||||||
|
var uciHttpsConsoleUrl = '<%=https_console_url%>';
|
||||||
var gwToken = '';
|
var gwToken = '';
|
||||||
var statusUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "status_api")%>';
|
var statusUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "status_api")%>';
|
||||||
var tokenUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "get_token")%>';
|
var tokenUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclaw", "get_token")%>';
|
||||||
@@ -197,10 +219,34 @@ local port = uci:get("openclaw", "main", "port") or "18789"
|
|||||||
function getConsoleUrl() {
|
function getConsoleUrl() {
|
||||||
var host = window.location.hostname;
|
var host = window.location.hostname;
|
||||||
var url = 'http://' + host + ':' + gwPort + '/';
|
var url = 'http://' + host + ':' + gwPort + '/';
|
||||||
if (gwToken) url += '#token=' + encodeURIComponent(gwToken);
|
if (uciHttpsConsoleUrl && uciHttpsConsoleUrl.indexOf('https://') === 0) {
|
||||||
|
url = uciHttpsConsoleUrl;
|
||||||
|
if (url.indexOf('?') === -1 && url.indexOf('#') === -1 && url.slice(-1) !== '/') {
|
||||||
|
url += '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gwToken) {
|
||||||
|
url += (url.indexOf('#') === -1 ? '#' : '&') + 'token=' + encodeURIComponent(gwToken);
|
||||||
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateContextHint(url) {
|
||||||
|
var hintEl = document.getElementById('oc-console-context-hint');
|
||||||
|
var directBtn = document.getElementById('oc-console-direct-btn');
|
||||||
|
if (directBtn) directBtn.href = url;
|
||||||
|
var isHttps = (url.indexOf('https://') === 0);
|
||||||
|
var isSecure = (typeof window.isSecureContext !== 'undefined') ? window.isSecureContext : (location.protocol === 'https:' || location.hostname === 'localhost' || location.hostname === '127.0.0.1');
|
||||||
|
|
||||||
|
if (hintEl) {
|
||||||
|
if (!isHttps && !isSecure && location.protocol !== 'https:') {
|
||||||
|
hintEl.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
hintEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleFullScreen() {
|
function toggleFullScreen() {
|
||||||
if (!document.fullscreenElement && !document.webkitFullscreenElement) {
|
if (!document.fullscreenElement && !document.webkitFullscreenElement) {
|
||||||
if (wrapEl.requestFullscreen) {
|
if (wrapEl.requestFullscreen) {
|
||||||
@@ -227,7 +273,11 @@ local port = uci:get("openclaw", "main", "port") or "18789"
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkAndLoad() {
|
function checkAndLoad() {
|
||||||
addrEl.textContent = window.location.hostname + ':' + gwPort;
|
if (uciHttpsConsoleUrl && uciHttpsConsoleUrl.indexOf('https://') === 0) {
|
||||||
|
addrEl.textContent = uciHttpsConsoleUrl;
|
||||||
|
} else {
|
||||||
|
addrEl.textContent = window.location.hostname + ':' + gwPort;
|
||||||
|
}
|
||||||
|
|
||||||
(new XHR()).post(tokenUrl, { token: ocCsrfToken }, function(tx) {
|
(new XHR()).post(tokenUrl, { token: ocCsrfToken }, function(tx) {
|
||||||
try {
|
try {
|
||||||
@@ -247,7 +297,8 @@ local port = uci:get("openclaw", "main", "port") or "18789"
|
|||||||
statusTextEl.innerHTML = '<span style="color:#1a7f37;">● 网关运行中</span>';
|
statusTextEl.innerHTML = '<span style="color:#1a7f37;">● 网关运行中</span>';
|
||||||
openBtn.href = url;
|
openBtn.href = url;
|
||||||
actionsEl.style.display = 'flex';
|
actionsEl.style.display = 'flex';
|
||||||
if (location.protocol === 'https:') {
|
updateContextHint(url);
|
||||||
|
if (location.protocol === 'https:' && url.indexOf('https://') !== 0) {
|
||||||
var hintEl = document.getElementById('oc-console-https-hint');
|
var hintEl = document.getElementById('oc-console-https-hint');
|
||||||
if (hintEl) hintEl.style.display = 'block';
|
if (hintEl) hintEl.style.display = 'block';
|
||||||
}
|
}
|
||||||
@@ -331,7 +382,16 @@ local port = uci:get("openclaw", "main", "port") or "18789"
|
|||||||
(new XHR()).get(devicesListUrl, null, function(x) {
|
(new XHR()).get(devicesListUrl, null, function(x) {
|
||||||
try {
|
try {
|
||||||
var data = JSON.parse(x.responseText);
|
var data = JSON.parse(x.responseText);
|
||||||
var pending = (data && data.pending) || [];
|
if (!data || data.status !== 'ok') {
|
||||||
|
if (pairingBadge) pairingBadge.textContent = '!';
|
||||||
|
var errMsg = (data && data.message) || '查询设备列表失败';
|
||||||
|
if (manualOpen) {
|
||||||
|
pairingItems.innerHTML = '<div style="color:#cf222e; font-size:12px;">❌ ' + escapeHtml(errMsg) + '</div>';
|
||||||
|
pairingBanner.style.display = 'block';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pending = data.pending || [];
|
||||||
if (pairingBadge) pairingBadge.textContent = pending.length;
|
if (pairingBadge) pairingBadge.textContent = pending.length;
|
||||||
var navBadge = document.getElementById('oc-console-pairing-nav-badge');
|
var navBadge = document.getElementById('oc-console-pairing-nav-badge');
|
||||||
if (navBadge) {
|
if (navBadge) {
|
||||||
@@ -396,14 +456,22 @@ local port = uci:get("openclaw", "main", "port") or "18789"
|
|||||||
try {
|
try {
|
||||||
var res = JSON.parse(x.responseText);
|
var res = JSON.parse(x.responseText);
|
||||||
if (res.status === 'ok') {
|
if (res.status === 'ok') {
|
||||||
pairingMsg.style.color = '#52c41a';
|
if ((res.success_count || 0) > 0) {
|
||||||
pairingMsg.innerHTML = '✅ ' + (res.message || '批准成功!') + ' 正在重新载入控制台...';
|
pairingMsg.style.color = '#52c41a';
|
||||||
setTimeout(function() {
|
pairingMsg.innerHTML = '✅ ' + (res.message || '批准成功!') + ' 正在重新载入控制台...';
|
||||||
checkDevices(false);
|
setTimeout(function() {
|
||||||
if (iframeEl) {
|
checkDevices(false);
|
||||||
iframeEl.src = getConsoleUrl();
|
if (iframeEl) {
|
||||||
}
|
iframeEl.src = getConsoleUrl();
|
||||||
}, 1500);
|
}
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
pairingMsg.style.color = '#faad14';
|
||||||
|
pairingMsg.innerHTML = 'ℹ️ ' + (res.message || '当前没有待配对的设备请求');
|
||||||
|
setTimeout(function() {
|
||||||
|
checkDevices(false);
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
pairingMsg.style.color = '#cf222e';
|
pairingMsg.style.color = '#cf222e';
|
||||||
pairingMsg.innerHTML = '❌ ' + (res.message || '批准失败');
|
pairingMsg.innerHTML = '❌ ' + (res.message || '批准失败');
|
||||||
|
|||||||
@@ -74,9 +74,18 @@ for f in "$CONSOLE" "$ADVANCED"; do
|
|||||||
grep -Fq "escapeHtml(item.requestId" "$f" || fail "$(basename "$f") must escape requestId"
|
grep -Fq "escapeHtml(item.requestId" "$f" || fail "$(basename "$f") must escape requestId"
|
||||||
done
|
done
|
||||||
|
|
||||||
# ── 7. 控制器退出码与参数完整性契约 ──
|
# ── 7. 控制器退出码与错误分支解析契约 ──
|
||||||
grep -Fq "__EXIT" "$CONTROLLER" || fail "controller must check command exit code"
|
grep -Fq "__EXIT" "$CONTROLLER" || fail "controller must check command exit code"
|
||||||
grep -Fq "缺少 request_id 或 all 参数" "$CONTROLLER" || fail "controller must reject empty approve requests"
|
grep -Fq "缺少 request_id 或 all 参数" "$CONTROLLER" || fail "controller must reject empty approve requests"
|
||||||
|
grep -Fq "if not output or exit_code ~= 0 then" "$CONTROLLER" || fail "controller must reject non-zero exit_code in devices_list"
|
||||||
|
grep -Fq "if not list_out or list_code ~= 0 then" "$CONTROLLER" || fail "controller must reject non-zero exit_code when querying devices list in approve_all"
|
||||||
|
grep -Fq "解析设备列表失败" "$CONTROLLER" || fail "controller must report error on JSON parse failure in devices_list"
|
||||||
|
|
||||||
|
# ── 8. 前端状态与批准语义契约 ──
|
||||||
|
for f in "$CONSOLE" "$ADVANCED"; do
|
||||||
|
grep -Fq "success_count" "$f" || fail "$(basename "$f") must check success_count to prevent false reload on empty list"
|
||||||
|
grep -Fq "data.status !==" "$f" || fail "$(basename "$f") must check data.status before consuming devices list"
|
||||||
|
done
|
||||||
|
|
||||||
# ── 8. 换行符约束 (LF only) ──
|
# ── 8. 换行符约束 (LF only) ──
|
||||||
cr=$(printf '\r')
|
cr=$(printf '\r')
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# LuCI 基本设置页面渲染与变量拼接契约测试。
|
||||||
|
# 覆盖:
|
||||||
|
# 1. service_ctl_url 与 ctl_url 变量定义完整性,避免 nil 拼接崩溃
|
||||||
|
# 2. XHR post 请求 URL 必须非空且有效
|
||||||
|
# 3. Lua 解释器环境下实际执行 act.cfgvalue() 渲染无报错
|
||||||
|
# 4. LF 换行符约束
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||||
|
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
||||||
|
BASIC_MODEL="$REPO_ROOT/luasrc/model/cbi/openclaw/basic.lua"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "FAIL: $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -f "$BASIC_MODEL" ] || fail "missing $BASIC_MODEL"
|
||||||
|
|
||||||
|
# ── 1. 变量定义完整性 ──
|
||||||
|
grep -Fq "local ctl_url =" "$BASIC_MODEL" || fail "basic.lua must define ctl_url"
|
||||||
|
grep -Fq "local service_ctl_url =" "$BASIC_MODEL" || fail "basic.lua must define service_ctl_url"
|
||||||
|
grep -Fq 'service_ctl_url' "$BASIC_MODEL" || fail "basic.lua must reference service_ctl_url"
|
||||||
|
|
||||||
|
# ── 2. Lua 运行时执行契约 ──
|
||||||
|
lua_bin=""
|
||||||
|
for cand in lua5.1 lua; do
|
||||||
|
command -v "$cand" >/dev/null 2>&1 && { lua_bin="$cand"; break; }
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$lua_bin" ]; then
|
||||||
|
"$lua_bin" -e '
|
||||||
|
package.path = "./luasrc/?.lua;" .. package.path
|
||||||
|
_G.luci = {
|
||||||
|
sys = { exec = function() return "" end },
|
||||||
|
dispatcher = {
|
||||||
|
build_url = function(...) return "/" .. table.concat({...}, "/") end,
|
||||||
|
context = { authtoken = "dummy-csrf-token" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
package.loaded["luci.sys"] = _G.luci.sys
|
||||||
|
package.loaded["luci.dispatcher"] = _G.luci.dispatcher
|
||||||
|
_G.Map = function(...)
|
||||||
|
return {
|
||||||
|
section = function(...)
|
||||||
|
return {
|
||||||
|
option = function(self, opt, name)
|
||||||
|
local o = {}
|
||||||
|
if name == "_actions" then _G.act = o end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
_G.SimpleSection = {}
|
||||||
|
_G.DummyValue = {}
|
||||||
|
|
||||||
|
dofile("'"$BASIC_MODEL"'")
|
||||||
|
|
||||||
|
if not _G.act or type(_G.act.cfgvalue) ~= "function" then
|
||||||
|
error("act.cfgvalue function not exported")
|
||||||
|
end
|
||||||
|
|
||||||
|
local rendered = _G.act.cfgvalue({}, {})
|
||||||
|
if type(rendered) ~= "string" or #rendered < 1000 then
|
||||||
|
error("cfgvalue rendered content too short or invalid type: " .. type(rendered))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not rendered:find("/admin/services/openclaw/service_ctl") then
|
||||||
|
error("rendered output missing service_ctl endpoint URL")
|
||||||
|
end
|
||||||
|
|
||||||
|
if rendered:find("post%(\"\",") or rendered:find("post%(undefined,") or rendered:find("post%(null,") then
|
||||||
|
error("rendered output has empty or undefined XHR post URL")
|
||||||
|
end
|
||||||
|
' || fail "basic.lua failed to execute or render cleanly in Lua"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 3. 换行符约束 (LF only) ──
|
||||||
|
cr=$(printf '\r')
|
||||||
|
if LC_ALL=C grep -q "$cr" "$BASIC_MODEL"; then
|
||||||
|
fail "$(basename "$BASIC_MODEL") must use LF line endings"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ok"
|
||||||
@@ -10,7 +10,12 @@ fail() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
grep -Fq "var url = 'http://' + host + ':' + gwPort + '/'" "$CONSOLE_VIEW" || fail "console view should force HTTP gateway URL"
|
grep -Fq "https_console_url" "$CONSOLE_VIEW" || fail "console view should extract and validate https_console_url from UCI"
|
||||||
|
grep -Fq "uciHttpsConsoleUrl" "$CONSOLE_VIEW" || fail "console view should expose uciHttpsConsoleUrl to JavaScript"
|
||||||
|
grep -Fq "'http://' + host + ':' + gwPort" "$CONSOLE_VIEW" || fail "console view should fall back to HTTP host:port"
|
||||||
|
grep -Fq "新窗口直达" "$CONSOLE_VIEW" || fail "console view should provide new window direct access"
|
||||||
|
grep -Fq "访问条件提示" "$CONSOLE_VIEW" || fail "console view should provide access conditions hint for secure context"
|
||||||
|
grep -Fq "isSecureContext" "$CONSOLE_VIEW" || fail "console view should check isSecureContext"
|
||||||
grep -Fq "document.createElement('iframe')" "$CONSOLE_VIEW" || fail "console view should embed the OpenClaw UI in an iframe"
|
grep -Fq "document.createElement('iframe')" "$CONSOLE_VIEW" || fail "console view should embed the OpenClaw UI in an iframe"
|
||||||
grep -Fq "oc-console-iframe" "$CONSOLE_VIEW" || fail "console view should define oc-console-iframe"
|
grep -Fq "oc-console-iframe" "$CONSOLE_VIEW" || fail "console view should define oc-console-iframe"
|
||||||
grep -Fq "allowfullscreen" "$CONSOLE_VIEW" || fail "console view should support fullscreen"
|
grep -Fq "allowfullscreen" "$CONSOLE_VIEW" || fail "console view should support fullscreen"
|
||||||
|
|||||||
@@ -181,8 +181,11 @@ grep -q "yiwanai/gpt-5.5" root/usr/share/openclaw/oc-config.sh || fail "shell Yi
|
|||||||
grep -q "anthropic-compatible" root/usr/share/openclaw/oc-config.sh || fail "shell custom Anthropic provider missing"
|
grep -q "anthropic-compatible" root/usr/share/openclaw/oc-config.sh || fail "shell custom Anthropic provider missing"
|
||||||
grep -q "anthropic-messages" root/usr/share/openclaw/oc-config.sh || fail "shell custom Anthropic provider mode missing"
|
grep -q "anthropic-messages" root/usr/share/openclaw/oc-config.sh || fail "shell custom Anthropic provider mode missing"
|
||||||
|
|
||||||
grep -q "var url = 'http://'" luasrc/view/openclaw/console.htm || fail "console must force HTTP gateway URL"
|
grep -q "uciHttpsConsoleUrl" luasrc/view/openclaw/console.htm || fail "console must support HTTPS console url"
|
||||||
|
grep -q "var url = 'http://'" luasrc/view/openclaw/console.htm || fail "console must fallback to HTTP gateway URL"
|
||||||
|
grep -q "新窗口直达" luasrc/view/openclaw/console.htm || fail "console must expose a new-window direct entry"
|
||||||
grep -q "新窗口打开" luasrc/view/openclaw/console.htm || fail "console must expose a new-window entry"
|
grep -q "新窗口打开" luasrc/view/openclaw/console.htm || fail "console must expose a new-window entry"
|
||||||
|
grep -q "访问条件提示" luasrc/view/openclaw/console.htm || fail "console must provide access condition hint"
|
||||||
grep -q "document.createElement('iframe')" luasrc/view/openclaw/console.htm || fail "console must embed OpenClaw in an iframe"
|
grep -q "document.createElement('iframe')" luasrc/view/openclaw/console.htm || fail "console must embed OpenClaw in an iframe"
|
||||||
|
|
||||||
grep -q "root/usr/libexec" scripts/build_ipk.sh || fail "ipk script must package shell helpers"
|
grep -q "root/usr/libexec" scripts/build_ipk.sh || fail "ipk script must package shell helpers"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.9.9
|
PKG_VERSION:=26.9.9
|
||||||
PKG_RELEASE:=264
|
PKG_RELEASE:=265
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -181,9 +181,7 @@ o:value("1:65535", translate("All"))
|
|||||||
o:depends("mode", "1")
|
o:depends("mode", "1")
|
||||||
o.validate = port_validate
|
o.validate = port_validate
|
||||||
|
|
||||||
o = s:taboption("Main", DummyValue, "_hide_node_option", "")
|
o = s:taboption("Main", HideValue, "_hide_node_option", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = "1"
|
|
||||||
o:depends("mode", "0")
|
o:depends("mode", "0")
|
||||||
o:depends({ tcp_no_redir_ports = "1:65535", udp_no_redir_ports = "1:65535" })
|
o:depends({ tcp_no_redir_ports = "1:65535", udp_no_redir_ports = "1:65535" })
|
||||||
if TCP_NO_REDIR_PORTS == "1:65535" and UDP_NO_REDIR_PORTS == "1:65535" then
|
if TCP_NO_REDIR_PORTS == "1:65535" and UDP_NO_REDIR_PORTS == "1:65535" then
|
||||||
@@ -208,19 +206,14 @@ if node_value then
|
|||||||
end
|
end
|
||||||
current_node = current_node_id and m:get(current_node_id) or {}
|
current_node = current_node_id and m:get(current_node_id) or {}
|
||||||
|
|
||||||
o = s:taboption("Main", DummyValue, "node_save_before", "")
|
o = s:taboption("Main", HideValue, "node_save_before", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = current_node[".name"]
|
o.value = current_node[".name"]
|
||||||
o.cbid = function(self, section) return "node_save_before" end
|
o.cbid = function(self, section) return "node_save_before" end
|
||||||
|
|
||||||
o = s:taboption("Main", DummyValue, "_acl_node_bool", "")
|
o = s:taboption("Main", HideValue, "_acl_node_bool", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = "1"
|
|
||||||
o:depends({ node = "", ['!reverse'] = true })
|
o:depends({ node = "", ['!reverse'] = true })
|
||||||
|
|
||||||
o = s:taboption("Main", DummyValue, "_diff_global_node", "")
|
o = s:taboption("Main", HideValue, "_diff_global_node", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = "1"
|
|
||||||
if NODE ~= "" then
|
if NODE ~= "" then
|
||||||
o:depends({ node = NODE, ['!reverse'] = true })
|
o:depends({ node = NODE, ['!reverse'] = true })
|
||||||
else
|
else
|
||||||
@@ -319,14 +312,10 @@ o.template = m:template_path("/global/proxy")
|
|||||||
o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
|
o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
|
||||||
|
|
||||||
-- Node → DNS Depends Settings
|
-- Node → DNS Depends Settings
|
||||||
o = s:taboption("Main", DummyValue, "_node_sel_shunt", "")
|
o = s:taboption("Main", HideValue, "_node_sel_shunt", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = "1"
|
|
||||||
o:depends({ node = "__always__" })
|
o:depends({ node = "__always__" })
|
||||||
|
|
||||||
o = s:taboption("Main", DummyValue, "_node_sel_other", "")
|
o = s:taboption("Main", HideValue, "_node_sel_other", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = "1"
|
|
||||||
o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
|
o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
|
||||||
|
|
||||||
---- DNS
|
---- DNS
|
||||||
|
|||||||
@@ -162,25 +162,18 @@ o = s:taboption("Main", Flag, "node_socks_bind_local", translate("Node") .. " So
|
|||||||
o.default = "1"
|
o.default = "1"
|
||||||
o:depends("_node", "1")
|
o:depends("_node", "1")
|
||||||
|
|
||||||
o = s:taboption("Main", DummyValue, "node_save_before", "")
|
o = s:taboption("Main", HideValue, "node_save_before", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = current_node[".name"]
|
o.value = current_node[".name"]
|
||||||
o.cbid = function(self, section) return "node_save_before" end
|
o.cbid = function(self, section) return "node_save_before" end
|
||||||
|
|
||||||
o = s:taboption("Main", DummyValue, "_node", "")
|
o = s:taboption("Main", HideValue, "_node", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = "1"
|
|
||||||
o:depends({ node = "", ['!reverse'] = true })
|
o:depends({ node = "", ['!reverse'] = true })
|
||||||
|
|
||||||
-- Node → DNS Depends Settings
|
-- Node → DNS Depends Settings
|
||||||
o = s:taboption("Main", DummyValue, "_node_sel_shunt", "")
|
o = s:taboption("Main", HideValue, "_node_sel_shunt", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = "1"
|
|
||||||
o:depends({ node = "__always__" })
|
o:depends({ node = "__always__" })
|
||||||
|
|
||||||
o = s:taboption("Main", DummyValue, "_node_sel_other", "")
|
o = s:taboption("Main", HideValue, "_node_sel_other", "")
|
||||||
o.template = m:template_path("/cbi/hidevalue")
|
|
||||||
o.value = "1"
|
|
||||||
o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
|
o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
|
||||||
|
|
||||||
-- [[ DNS Settings ]]--
|
-- [[ DNS Settings ]]--
|
||||||
|
|||||||
@@ -1517,11 +1517,111 @@ function set_default_cbi()
|
|||||||
return cbi.AbstractValue.write(self, section, new_val)
|
return cbi.AbstractValue.write(self, section, new_val)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if true then
|
||||||
|
--HideValue
|
||||||
|
local HideValue = util.class(cbi.DummyValue)
|
||||||
|
function HideValue.__init__(self, ...)
|
||||||
|
cbi.DummyValue.__init__(self, ...)
|
||||||
|
self.template = self.map:template_path("/cbi/hidevalue")
|
||||||
|
self.value = "1"
|
||||||
|
end
|
||||||
|
cbi.HideValue = HideValue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function set_type_cbi(s)
|
||||||
|
local cbi = require "luci.cbi"
|
||||||
|
local s1 = s.parent
|
||||||
|
function s.option(s_self, class, option, ...)
|
||||||
|
local obj = class(s_self.map, s_self, option, ...)
|
||||||
|
obj.config_option = option
|
||||||
|
obj.option_prefix = s_self.option_prefix
|
||||||
|
obj.option = s_self.option_prefix .. option
|
||||||
|
obj.cfgvalue = function(self, section)
|
||||||
|
if self.rewrite_option then
|
||||||
|
return self.map:get(section, self.rewrite_option)
|
||||||
|
else
|
||||||
|
return self.map:get(section, self.config_option)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
obj.write = function(self, section, value)
|
||||||
|
if s1.fields["type"]:formvalue(s_self.section) == s_self.type_name then
|
||||||
|
local new_val = value
|
||||||
|
if util.instanceof(self, cbi.DynamicList) then
|
||||||
|
local new_t = {}
|
||||||
|
if type(value) == "table" then
|
||||||
|
new_t = table_remove_duplicates(value)
|
||||||
|
else
|
||||||
|
new_t = { value }
|
||||||
|
end
|
||||||
|
if self.cast == "string" then
|
||||||
|
new_val = table.concat(new_t, " ")
|
||||||
|
else
|
||||||
|
new_val = new_t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.rewrite_option then
|
||||||
|
self.map:set(section, self.rewrite_option, new_val)
|
||||||
|
else
|
||||||
|
self.map:set(section, self.config_option, new_val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
obj.remove = function(self, section)
|
||||||
|
if s1.fields["type"]:formvalue(s_self.section) == s_self.type_name then
|
||||||
|
if self.rewrite_option then
|
||||||
|
self.map:del(section, self.rewrite_option)
|
||||||
|
else
|
||||||
|
self.map:del(section, self.config_option)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
obj.deplist2json = function(self, section, deplist)
|
||||||
|
local deps, i, d = { }
|
||||||
|
if type(self.deps) == "table" then
|
||||||
|
if not next(self.deps) then
|
||||||
|
self:depends({ type = s.type_name })
|
||||||
|
end
|
||||||
|
local list = deplist or self.deps
|
||||||
|
for i, d in ipairs(list) do
|
||||||
|
if s.type_name and not d["type"] then
|
||||||
|
d["type"] = s.type_name
|
||||||
|
end
|
||||||
|
local a, k, v = { }
|
||||||
|
for k, v in pairs(d) do
|
||||||
|
if k:find("!", 1, true) then
|
||||||
|
a[k] = v
|
||||||
|
elseif k:find("^", 1, true) then
|
||||||
|
a[k:sub(2)] = v
|
||||||
|
elseif k:find(".", 1, true) then
|
||||||
|
a['cbid%s' % k] = v
|
||||||
|
elseif s_self.fields[k] then
|
||||||
|
a['cbid.%s.%s.%s' %{ self.config, section, s_self.fields[k].option }] = v
|
||||||
|
else
|
||||||
|
a['cbid.%s.%s.%s' %{ self.config, section, k }] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deps[#deps+1] = a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return util.serialize_json(deps)
|
||||||
|
end
|
||||||
|
s_self:append(obj)
|
||||||
|
s_self.fields[option] = obj
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function type_cbi_section(s, s2)
|
||||||
|
for i, v in ipairs(s2.children) do
|
||||||
|
local o = s2.children[i]
|
||||||
|
s:append(o)
|
||||||
|
s.fields[o.option] = o
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function return_map(map)
|
function return_map(map)
|
||||||
local cbi = require "luci.cbi"
|
local cbi = require "luci.cbi"
|
||||||
local api = require "luci.passwall.api"
|
|
||||||
if true then
|
if true then
|
||||||
-- header
|
-- header
|
||||||
local header = cbi.Template(appname .. "/cbi/header")
|
local header = cbi.Template(appname .. "/cbi/header")
|
||||||
@@ -1537,91 +1637,6 @@ function return_map(map)
|
|||||||
return map
|
return map
|
||||||
end
|
end
|
||||||
|
|
||||||
function set_type_cbi(s)
|
|
||||||
local cbi = require "luci.cbi"
|
|
||||||
local s1 = s.parent
|
|
||||||
function s.option(self, class, option, ...)
|
|
||||||
local obj = class(self.map, self, option, ...)
|
|
||||||
obj.config_option = option
|
|
||||||
obj.option_prefix = self.option_prefix
|
|
||||||
obj.option = self.option_prefix .. option
|
|
||||||
obj.cfgvalue = function(o_self, section)
|
|
||||||
if o_self.rewrite_option then
|
|
||||||
return o_self.map:get(section, o_self.rewrite_option)
|
|
||||||
else
|
|
||||||
return o_self.map:get(section, o_self.config_option)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
obj.write = function(o_self, section, value)
|
|
||||||
if s1.fields["type"]:formvalue(self.section) == self.type_name then
|
|
||||||
local new_val = value
|
|
||||||
if util.instanceof(o_self, cbi.DynamicList) then
|
|
||||||
local new_t = {}
|
|
||||||
if type(value) == "table" then
|
|
||||||
new_t = table_remove_duplicates(value)
|
|
||||||
else
|
|
||||||
new_t = { value }
|
|
||||||
end
|
|
||||||
if o_self.cast == "string" then
|
|
||||||
new_val = table.concat(new_t, " ")
|
|
||||||
else
|
|
||||||
new_val = new_t
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if o_self.rewrite_option then
|
|
||||||
o_self.map:set(section, o_self.rewrite_option, new_val)
|
|
||||||
else
|
|
||||||
o_self.map:set(section, o_self.config_option, new_val)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
obj.remove = function(o_self, section)
|
|
||||||
if s1.fields["type"]:formvalue(self.section) == self.type_name then
|
|
||||||
if o_self.rewrite_option then
|
|
||||||
o_self.map:del(section, o_self.rewrite_option)
|
|
||||||
else
|
|
||||||
o_self.map:del(section, o_self.config_option)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
obj.depends = function(o_self, field, value)
|
|
||||||
local deps
|
|
||||||
if type(field) == "string" then
|
|
||||||
deps = {}
|
|
||||||
deps[field] = value
|
|
||||||
else
|
|
||||||
deps = field
|
|
||||||
end
|
|
||||||
local new_deps = {}
|
|
||||||
for k, v in pairs(deps) do
|
|
||||||
local n_k = k
|
|
||||||
if self.fields[k] then
|
|
||||||
n_k = self.fields[k].option
|
|
||||||
end
|
|
||||||
new_deps[n_k] = v
|
|
||||||
end
|
|
||||||
new_deps["type"] = self.type_name
|
|
||||||
if next(new_deps) then
|
|
||||||
table.insert(o_self.deps, new_deps)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:append(obj)
|
|
||||||
self.fields[option] = obj
|
|
||||||
return obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function type_cbi_section(s, s2)
|
|
||||||
for i, v in ipairs(s2.children) do
|
|
||||||
local o = s2.children[i]
|
|
||||||
if #o.deps == 0 then
|
|
||||||
o:depends({ type = s2.type_name })
|
|
||||||
end
|
|
||||||
s:append(o)
|
|
||||||
s.fields[o.option] = o
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function get_std_domain(domain)
|
function get_std_domain(domain)
|
||||||
domain = trim(domain)
|
domain = trim(domain)
|
||||||
if domain == "" then return "" end
|
if domain == "" then return "" end
|
||||||
|
|||||||
@@ -337,10 +337,10 @@ run_socks() {
|
|||||||
[ -n "$config_file" ] && [ -z "$(echo ${config_file} | grep $TMP_PATH)" ] && config_file=$TMP_PATH/$config_file
|
[ -n "$config_file" ] && [ -z "$(echo ${config_file} | grep $TMP_PATH)" ] && config_file=$TMP_PATH/$config_file
|
||||||
[ -n "$http_port" ] || http_port=0
|
[ -n "$http_port" ] || http_port=0
|
||||||
[ -n "$http_config_file" ] && [ -z "$(echo ${http_config_file} | grep $TMP_PATH)" ] && http_config_file=$TMP_PATH/$http_config_file
|
[ -n "$http_config_file" ] && [ -z "$(echo ${http_config_file} | grep $TMP_PATH)" ] && http_config_file=$TMP_PATH/$http_config_file
|
||||||
if [ -n "$log_file" ] && [ "$log_file" != "/dev/null" ] && [ -z "$(echo ${log_file} | grep $TMP_PATH)" ]; then
|
if [ -z "$log_file" ] || [ "$log_file" = "/dev/null" ]; then
|
||||||
log_file=$TMP_PATH/$log_file
|
|
||||||
else
|
|
||||||
log_file="/dev/null"
|
log_file="/dev/null"
|
||||||
|
elif [ "${log_file#"$TMP_PATH/"}" = "$log_file" ]; then
|
||||||
|
log_file=$TMP_PATH/$log_file
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z')
|
local type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z')
|
||||||
@@ -1580,8 +1580,9 @@ acl_app() {
|
|||||||
}
|
}
|
||||||
local _redir_port=$(get_cache_var "node_${node}_redir_port")
|
local _redir_port=$(get_cache_var "node_${node}_redir_port")
|
||||||
local _socks_port=$(get_cache_var "node_${node}_socks_port")
|
local _socks_port=$(get_cache_var "node_${node}_socks_port")
|
||||||
|
local _enable_log=$(get_cache_var "node_${node}_enable_log")
|
||||||
local _dns_port
|
local _dns_port
|
||||||
if [ -n "${_socks_port}" ] && [ -n "${_redir_port}" ]; then
|
if [ -n "${_socks_port}" ] && [ -n "${_redir_port}" ] && [ "${_enable_log}" != "1" ] && [ "${log}" != "1" ]; then
|
||||||
socks_port=${_socks_port}
|
socks_port=${_socks_port}
|
||||||
node_port=${_redir_port}
|
node_port=${_redir_port}
|
||||||
_dns_port=$(get_cache_var "node_${node}_${dns_cache_key}")
|
_dns_port=$(get_cache_var "node_${node}_${dns_cache_key}")
|
||||||
@@ -1593,7 +1594,10 @@ acl_app() {
|
|||||||
set_cache_var "node_${node}_redir_port" "${redir_port}"
|
set_cache_var "node_${node}_redir_port" "${redir_port}"
|
||||||
node_port=$redir_port
|
node_port=$redir_port
|
||||||
local log_file="/dev/null"
|
local log_file="/dev/null"
|
||||||
[ "${log}" = "1" ] && log_file="${TMP_ACL_PATH}/${sid}/node.log"
|
[ "${log}" = "1" ] && {
|
||||||
|
log_file="${TMP_ACL_PATH}/${sid}/node.log"
|
||||||
|
set_cache_var "node_${node}_enable_log" "1"
|
||||||
|
}
|
||||||
|
|
||||||
if [ "${type}" = "sing-box" ] || [ "${type}" = "xray" ]; then
|
if [ "${type}" = "sing-box" ] || [ "${type}" = "xray" ]; then
|
||||||
config_file="acl/${node}_${redir_port}.json"
|
config_file="acl/${node}_${redir_port}.json"
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk
|
|||||||
LUCI_TITLE:=LuCI shared OpenWrt auth bridge for LinkEase apps
|
LUCI_TITLE:=LuCI shared OpenWrt auth bridge for LinkEase apps
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
PKG_VERSION:=1.0.1
|
PKG_VERSION:=1.0.3
|
||||||
PKG_RELEASE:=4
|
PKG_RELEASE:=5
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
|||||||
FOOTSTRAP_VERSION?=
|
FOOTSTRAP_VERSION?=
|
||||||
ifneq ($(FOOTSTRAP_VERSION),)
|
ifneq ($(FOOTSTRAP_VERSION),)
|
||||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||||
PKG_RELEASE:=66
|
PKG_RELEASE:=68
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LUCI_TITLE:=Footstrap Theme
|
LUCI_TITLE:=Footstrap Theme
|
||||||
|
|||||||
@@ -195,12 +195,66 @@ function holdFloor(records) {
|
|||||||
}
|
}
|
||||||
if (!dirty.length) return;
|
if (!dirty.length) return;
|
||||||
|
|
||||||
|
/* THE SWEEP MAY NOT COST THE READER THE CLAMP IT EXISTS TO PREVENT — task resid. Clearing every
|
||||||
|
* floor before the measure pass is what makes the answers honest (above), and for the length of
|
||||||
|
* that pass the document stands without them. Measured live (chromium/owrt2512b @390 top normal,
|
||||||
|
* /admin/network/dhcp, `../tmp/task-resid/dbg-before.json`): 38 sweeps, 33 of them took the
|
||||||
|
* document DOWN between the clear and the write-back, and on 13 the offset went with it. Eight of
|
||||||
|
* those 13 are this fault — the unscoped, every-box sweep, document 4730px to 4729px and straight
|
||||||
|
* back to 4730px, offset 3886 to 3885 and NOT back. Nothing in this function is asynchronous and
|
||||||
|
* `scrolling()` at the top already refused a reader who is moving, so an offset that is LOWER
|
||||||
|
* after the write-back than before the clear was lowered by this pass and by nothing else — 0
|
||||||
|
* sweeps of the 38 moved it the other way.
|
||||||
|
*
|
||||||
|
* One pixel, and it is not the pixel that matters: `lateDrift()` reads the offset twice,
|
||||||
|
* SCROLL_IDLE apart, and treats any difference as "the reader has moved since" (its own comment
|
||||||
|
* below). That 1px discarded the whole 60px correction the same tick's shrink was owed
|
||||||
|
* (`late-refuse why: moving, seen 3886, now 3885`), and the unforced `rememberRest()` a
|
||||||
|
* millisecond later adopted the wrong offset as the reference the NEXT refill measures against:
|
||||||
|
* 59px off, carried forward, which is the 47-64px `REPEAT` reports on the second or third of
|
||||||
|
* three back-to-back refills of one section (`tools/scroll-anchor.mjs`, docs/anchoring.md).
|
||||||
|
*
|
||||||
|
* ONLY WHERE THE SCROLLER IS AS TALL AGAIN AS IT WAS, which is what separates this pass's own
|
||||||
|
* transient dip from a floor that came down because the CONTENT really shrank — the eight sweeps
|
||||||
|
* above against the other five, on the same run, that lost 3958 to 3886 with the document
|
||||||
|
* staying 120px shorter for good. That second clamp is real, it belongs to the shrink, and
|
||||||
|
* `lateDrift()`'s `floorShrink` path already corrects for it; touching it here was measured too
|
||||||
|
* — restoring unconditionally and letting the browser re-clamp the write is green on this cell
|
||||||
|
* as well, but it also makes every genuine shrink's clamp this file's OWN write
|
||||||
|
* (`sawOwnWrite()`), so the motion window that clamp used to open stops opening and
|
||||||
|
* `sampleMotion()`'s terminal sweep stops running with it. The narrow form holds the cell on its
|
||||||
|
* own, so the wider one does not ship.
|
||||||
|
*
|
||||||
|
* `writeOffset()` rather than a bare assignment: the restore is a scroll write like the two
|
||||||
|
* corrections, and the motion sampler must read it as this file's own rather than as the reader
|
||||||
|
* arriving. */
|
||||||
|
const sc = scroller(), page = sc || document.documentElement;
|
||||||
|
const at = scrollTop(), tall = page.scrollHeight;
|
||||||
dirty.forEach((box) => { box.style.minHeight = ''; });
|
dirty.forEach((box) => { box.style.minHeight = ''; });
|
||||||
dirty.forEach((box) => hs.push(box.offsetHeight));
|
dirty.forEach((box) => hs.push(box.offsetHeight));
|
||||||
dirty.forEach((box, i) => {
|
dirty.forEach((box, i) => {
|
||||||
if (hs[i] > 0) { box.style.minHeight = hs[i] + 'px'; box.setAttribute('data-fs-floor', ''); }
|
if (hs[i] > 0) { box.style.minHeight = hs[i] + 'px'; box.setAttribute('data-fs-floor', ''); }
|
||||||
else box.removeAttribute('data-fs-floor');
|
else box.removeAttribute('data-fs-floor');
|
||||||
});
|
});
|
||||||
|
/* AND WHERE IT IS A REAL SHRINK, SAY SO — task wk1440. The other side of the same test: the
|
||||||
|
* offset came down and the scroller stayed shorter, so the box really did give height up and
|
||||||
|
* the drop is the browser's clamp into it. `applyAnchor()` is the one thing that can put back
|
||||||
|
* the part of the shrink the clamp did NOT take, and it refuses while `scrolling()` — which the
|
||||||
|
* clamp's own scroll event has just made true. Measured (webkit/owrtsnapb @1440 side compact,
|
||||||
|
* /admin/status/overview, `../tmp/task-wk1440/`, the engine's own anchoring ablated away with
|
||||||
|
* `overflow-anchor: none` so the theme is the only corrector): a 120px pad removed above the
|
||||||
|
* reader took the scroller 2793 -> 2673px while the offset clamped 1889 -> 1829 — 60px short of
|
||||||
|
* the 1769 the reader needed, because a clamp only ever gives back what the document lost at its
|
||||||
|
* BOTTOM. One frame later `applyAnchor()` read `scrolling() true, 400ms left` and returned with
|
||||||
|
* a correct -60px correction in hand; the terminal sweep's `rememberRest()` then adopted 1829 as
|
||||||
|
* the reference, and every refill after it measured 0px drift against ground that was already
|
||||||
|
* 60px wrong — `3x repeat 0px/-60px/-60px`, the gate's "left the reader -60px off ... corrected
|
||||||
|
* never". Recorded as a PIXEL, not as a flag or a timestamp, and read back the way
|
||||||
|
* `sawOwnWrite()` reads its own: it stands only while the offset has not left it, so a reader
|
||||||
|
* who really does move clears it by moving. */
|
||||||
|
const landed = scrollTop();
|
||||||
|
if (landed >= at) return;
|
||||||
|
if (page.scrollHeight >= tall) writeOffset(sc, at); else _clampedTo = landed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- is the page moving right now? asked of the position, never of the events ----
|
/* ---- is the page moving right now? asked of the position, never of the events ----
|
||||||
@@ -266,6 +320,22 @@ function sawOwnWrite(y) {
|
|||||||
_ownWrite = null;
|
_ownWrite = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
/* Where the browser's own clamp last put the offset down, set by `holdFloor()`'s real-shrink branch
|
||||||
|
* and by nothing else — see the comment there for the measurement. NOT a second `_ownWrite`: this
|
||||||
|
* marker does not touch `scrolling()`, which keeps answering "the page is moving, whoever moves it"
|
||||||
|
* for the whole theme (task resid measured what happens when a clamp stops opening that window —
|
||||||
|
* `sampleMotion()`'s terminal sweep stops running behind it). It answers ONE narrower question, for
|
||||||
|
* `applyAnchor()` alone: is the motion that is blocking this correction the clamp the correction is
|
||||||
|
* FOR? */
|
||||||
|
let _clampedTo = null;
|
||||||
|
function sawClamp() {
|
||||||
|
/* the identical `scrollTop() !== seen` shape `lateDrift()` asks its own offset — the pixel was
|
||||||
|
* read out of `scrollTop()` in the first place, so equality is the whole test, and `null` is
|
||||||
|
* never equal to a number */
|
||||||
|
if (scrollTop() === _clampedTo) return true;
|
||||||
|
_clampedTo = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
/* The one place either correction may write the scroll position, so `_ownWrite` cannot go stale by a
|
/* The one place either correction may write the scroll position, so `_ownWrite` cannot go stale by a
|
||||||
* write skipping it. Reads the offset back rather than trusting the argument: a write near either
|
* write skipping it. Reads the offset back rather than trusting the argument: a write near either
|
||||||
* end of the document is clamped by the browser, and the pixel that actually lands is the one the
|
* end of the document is clamped by the browser, and the pixel that actually lands is the one the
|
||||||
@@ -1081,8 +1151,19 @@ function applyAnchor(ref) {
|
|||||||
if (!ref) return;
|
if (!ref) return;
|
||||||
/* not into a moving page: the correction is scheduled from the mutation and applied a frame
|
/* not into a moving page: the correction is scheduled from the mutation and applied a frame
|
||||||
* later, and a reader who starts scrolling in between would be put back onto a page they have
|
* later, and a reader who starts scrolling in between would be put back onto a page they have
|
||||||
* already left */
|
* already left.
|
||||||
if (scrolling()) return;
|
*
|
||||||
|
* UNLESS THE MOTION IS THE CLAMP THIS CORRECTION EXISTS FOR — task wk1440, the same trap
|
||||||
|
* `settleDeferredFloor()`'s own third attempt already fell into once ("gating on `scrolling()`
|
||||||
|
* refuses on the very motion it is trying to observe") and `lateDrift()` was built to avoid.
|
||||||
|
* A shrink above the reader clamps the offset down inside `holdFloor()`'s own synchronous pass,
|
||||||
|
* that clamp dispatches a `scroll` event of its own, and one frame later this guard reads it as
|
||||||
|
* a reader who started moving: measured on the cell this task closes, `apply-enter scrolling
|
||||||
|
* true, moving 400ms, at 1829` with a -60px correction already computed and never written.
|
||||||
|
* `sawClamp()` is the pixel `holdFloor()` watched the clamp land on, and it stands only while
|
||||||
|
* the offset has not left it — a reader who really is scrolling has moved off it by definition,
|
||||||
|
* so this reopens the guard for exactly one case and no other. */
|
||||||
|
if (scrolling() && !sawClamp()) return;
|
||||||
/* through scroller(), not a second probe: two copies of the same question can answer
|
/* through scroller(), not a second probe: two copies of the same question can answer
|
||||||
* differently within one frame */
|
* differently within one frame */
|
||||||
const sc = scroller();
|
const sc = scroller();
|
||||||
|
|||||||
+2
-2
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
|||||||
|
|
||||||
PKG_NAME:=natflow
|
PKG_NAME:=natflow
|
||||||
PKG_VERSION:=20260531
|
PKG_VERSION:=20260531
|
||||||
PKG_RELEASE:=80
|
PKG_RELEASE:=82
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_VERSION:=1c38f7df1a17128ee15c2b27f5b3360ee3256cda
|
PKG_SOURCE_VERSION:=ecd6455caed450157af8b59ffb616ef06a34ddb2
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||||
PKG_LICENSE:=GPL-2.0
|
PKG_LICENSE:=GPL-2.0
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
include $(INCLUDE_DIR)/kernel.mk
|
include $(INCLUDE_DIR)/kernel.mk
|
||||||
|
|
||||||
PKG_NAME:=shortcut-fe
|
PKG_NAME:=shortcut-fe
|
||||||
PKG_RELEASE:=10
|
PKG_RELEASE:=11
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
@@ -69,7 +69,6 @@ define Build/Compile
|
|||||||
$(KERNEL_MAKE_FLAGS) \
|
$(KERNEL_MAKE_FLAGS) \
|
||||||
$(PKG_MAKE_FLAGS) \
|
$(PKG_MAKE_FLAGS) \
|
||||||
M="$(PKG_BUILD_DIR)" \
|
M="$(PKG_BUILD_DIR)" \
|
||||||
EXTRA_CFLAGS+="-DSFE_SUPPORT_IPV6" \
|
|
||||||
SFE_SUPPORT_IPV6=y \
|
SFE_SUPPORT_IPV6=y \
|
||||||
modules
|
modules
|
||||||
endef
|
endef
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ endif
|
|||||||
|
|
||||||
obj-m += shortcut-fe-cm.o
|
obj-m += shortcut-fe-cm.o
|
||||||
|
|
||||||
|
ccflags-$(SFE_SUPPORT_IPV6) += -DSFE_SUPPORT_IPV6
|
||||||
|
|
||||||
shortcut-fe-objs := \
|
shortcut-fe-objs := \
|
||||||
sfe_ipv4.o
|
sfe_ipv4.o
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -8,12 +8,12 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=torrserver
|
PKG_NAME:=torrserver
|
||||||
PKG_VERSION:=MatriX.144
|
PKG_VERSION:=MatriX.144.2
|
||||||
PKG_RELEASE:=20
|
PKG_RELEASE:=21
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/YouROK/TorrServer.git
|
PKG_SOURCE_URL:=https://github.com/YouROK/TorrServer.git
|
||||||
PKG_SOURCE_VERSION:=c416a8f2b89b244c779f03f1f86e2f0f690c2668
|
PKG_SOURCE_VERSION:=aebd3895424c4de6063a2d89c279211ae8c9ad59
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||||
|
|||||||
Reference in New Issue
Block a user