mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-10 18:34:18 +08:00
🐶 Sync 2026-09-02 00:00:06
This commit is contained in:
+2
-2
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_BAIDUDRIVE:=$(ARCH)
|
||||
|
||||
PKG_NAME:=baidudrive
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.15
|
||||
PKG_RELEASE:=17
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.16
|
||||
PKG_RELEASE:=18
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/baidudrive-runtime-v$(PKG_VERSION)/
|
||||
PKG_HASH:=skip
|
||||
|
||||
+2
-2
@@ -6,9 +6,9 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dae
|
||||
PKG_VERSION:=2026.08.28
|
||||
PKG_RELEASE:=39
|
||||
PKG_RELEASE:=40
|
||||
|
||||
PKG_SOURCE:=dae-src-2026.08.28-cc57c26ba484.tar.gz
|
||||
PKG_SOURCE:=dae-src-2026.08.28-d6aca8a10b35.tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_HASH:=skip
|
||||
|
||||
+2
-2
@@ -6,9 +6,9 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=daed
|
||||
PKG_VERSION:=2026.08.28
|
||||
PKG_RELEASE:=50
|
||||
PKG_RELEASE:=51
|
||||
|
||||
PKG_SOURCE:=daed-src-2026.08.28-943c54572fb6.tar.gz
|
||||
PKG_SOURCE:=daed-src-2026.08.28-73f01b995c34.tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_HASH:=skip
|
||||
|
||||
@@ -9,8 +9,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_DOCKERMANAGER:=$(ARCH)
|
||||
|
||||
PKG_NAME:=dockermanager
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.15
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.16
|
||||
PKG_RELEASE:=4
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/dockermanager-runtime-v$(PKG_VERSION)/
|
||||
PKG_HASH:=skip
|
||||
|
||||
@@ -13,15 +13,52 @@
|
||||
"entry": "index.html",
|
||||
"basePath": "/apps/dockermanager/"
|
||||
},
|
||||
"config": {
|
||||
"providerOrder": ["uci"],
|
||||
"providers": {
|
||||
"uci": {
|
||||
"type": "uci"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"type": "http",
|
||||
"transport": "unix",
|
||||
"scheme": "http",
|
||||
"host": "127.0.0.1",
|
||||
"portFromUci": "dockermanager.@dockermanager[0].port",
|
||||
"defaultPort": 8192,
|
||||
"values": {
|
||||
"socketPath": {
|
||||
"default": "/var/run/dockermanager.sock",
|
||||
"keys": {
|
||||
"uci": "dockermanager.@dockermanager[0].socket_path"
|
||||
}
|
||||
},
|
||||
"listenMode": {
|
||||
"default": "auto",
|
||||
"keys": {
|
||||
"uci": "dockermanager.@dockermanager[0].listen_mode"
|
||||
}
|
||||
},
|
||||
"externalPortEnabled": {
|
||||
"default": false,
|
||||
"keys": {
|
||||
"uci": "dockermanager.@dockermanager[0].external_port_enabled"
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"default": 8192,
|
||||
"keys": {
|
||||
"uci": "dockermanager.@dockermanager[0].port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"upstreamBasePath": "/apps/dockermanager/",
|
||||
"apiPath": "api/",
|
||||
"pathMode": "preserve"
|
||||
"pathMode": "preserve",
|
||||
"fallback": {
|
||||
"transport": "tcp",
|
||||
"scheme": "http",
|
||||
"host": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"window": {
|
||||
"width": 1180,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
config dockermanager
|
||||
option 'enabled' '0'
|
||||
option 'data_dir' ''
|
||||
option 'listen_mode' 'auto'
|
||||
option 'external_port_enabled' '0'
|
||||
option 'socket_path' '/var/run/dockermanager.sock'
|
||||
option 'port' '8192'
|
||||
option 'bind_addr' '0.0.0.0'
|
||||
option 'base_path' '/apps/dockermanager/'
|
||||
|
||||
@@ -6,6 +6,9 @@ USE_PROCD=1
|
||||
get_config() {
|
||||
config_get_bool enabled "$1" enabled 0
|
||||
config_get data_dir "$1" data_dir ""
|
||||
config_get listen_mode "$1" listen_mode "auto"
|
||||
config_get_bool external_port_enabled "$1" external_port_enabled 0
|
||||
config_get socket_path "$1" socket_path "/var/run/dockermanager.sock"
|
||||
config_get port "$1" port "8192"
|
||||
config_get bind_addr "$1" bind_addr "0.0.0.0"
|
||||
config_get base_path "$1" base_path "/apps/dockermanager/"
|
||||
@@ -33,13 +36,25 @@ start_service() {
|
||||
|
||||
logger -t dockermanager "Starting Docker Manager service"
|
||||
procd_open_instance docker-manager
|
||||
procd_set_param env \
|
||||
DOCKER_MANAGER_ADDR="$bind_addr:$port" \
|
||||
DOCKER_MANAGER_BASE_PATH="$base_path" \
|
||||
DOCKER_MANAGER_DATA_DIR="$data_dir" \
|
||||
DOCKER_MANAGER_STATIC_ROOT="/usr/share/dockermanager/www" \
|
||||
DOCKER_MANAGER_PLATFORM="openwrt" \
|
||||
DOCKER_MANAGER_AUTH_MODE="openwrt_luci"
|
||||
if [ "$listen_mode" = "tcp" ] || [ "$external_port_enabled" = 1 ]; then
|
||||
procd_set_param env \
|
||||
DOCKER_MANAGER_LISTEN_MODE="tcp" \
|
||||
DOCKER_MANAGER_ADDR="$bind_addr:$port" \
|
||||
DOCKER_MANAGER_BASE_PATH="$base_path" \
|
||||
DOCKER_MANAGER_DATA_DIR="$data_dir" \
|
||||
DOCKER_MANAGER_STATIC_ROOT="/usr/share/dockermanager/www" \
|
||||
DOCKER_MANAGER_PLATFORM="openwrt" \
|
||||
DOCKER_MANAGER_AUTH_MODE="openwrt_luci"
|
||||
else
|
||||
procd_set_param env \
|
||||
DOCKER_MANAGER_LISTEN_MODE="unix" \
|
||||
DOCKER_MANAGER_SOCKET_PATH="$socket_path" \
|
||||
DOCKER_MANAGER_BASE_PATH="$base_path" \
|
||||
DOCKER_MANAGER_DATA_DIR="$data_dir" \
|
||||
DOCKER_MANAGER_STATIC_ROOT="/usr/share/dockermanager/www" \
|
||||
DOCKER_MANAGER_PLATFORM="openwrt" \
|
||||
DOCKER_MANAGER_AUTH_MODE="openwrt_luci"
|
||||
fi
|
||||
procd_set_param command /usr/sbin/docker-manager
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_kaiplus:=$(ARCH)
|
||||
|
||||
PKG_NAME:=kaiplus
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.15
|
||||
PKG_RELEASE:=7
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.16
|
||||
PKG_RELEASE:=8
|
||||
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_HASH:=skip
|
||||
|
||||
@@ -13,15 +13,52 @@
|
||||
"entry": "index.html",
|
||||
"basePath": "/apps/kaiplus/"
|
||||
},
|
||||
"config": {
|
||||
"providerOrder": ["uci"],
|
||||
"providers": {
|
||||
"uci": {
|
||||
"type": "uci"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"type": "http",
|
||||
"transport": "unix",
|
||||
"scheme": "http",
|
||||
"host": "127.0.0.1",
|
||||
"portFromUci": "kaiplus.@kaiplus[0].port",
|
||||
"defaultPort": 8189,
|
||||
"values": {
|
||||
"socketPath": {
|
||||
"default": "/var/run/kaiplus.sock",
|
||||
"keys": {
|
||||
"uci": "kaiplus.@kaiplus[0].socket_path"
|
||||
}
|
||||
},
|
||||
"listenMode": {
|
||||
"default": "auto",
|
||||
"keys": {
|
||||
"uci": "kaiplus.@kaiplus[0].listen_mode"
|
||||
}
|
||||
},
|
||||
"externalPortEnabled": {
|
||||
"default": false,
|
||||
"keys": {
|
||||
"uci": "kaiplus.@kaiplus[0].external_port_enabled"
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"default": 8189,
|
||||
"keys": {
|
||||
"uci": "kaiplus.@kaiplus[0].port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"upstreamBasePath": "/apps/kaiplus/",
|
||||
"apiPath": "api/",
|
||||
"pathMode": "preserve"
|
||||
"pathMode": "preserve",
|
||||
"fallback": {
|
||||
"transport": "tcp",
|
||||
"scheme": "http",
|
||||
"host": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"window": {
|
||||
"width": 1180,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
config kaiplus
|
||||
option 'enabled' '0'
|
||||
option 'data_dir' ''
|
||||
option 'listen_mode' 'auto'
|
||||
option 'external_port_enabled' '0'
|
||||
option 'socket_path' '/var/run/kaiplus.sock'
|
||||
option 'port' '8189'
|
||||
option 'bind_addr' '0.0.0.0'
|
||||
option 'base_path' '/apps/kaiplus/'
|
||||
option 'system_role' 'istoreos'
|
||||
|
||||
@@ -4,9 +4,13 @@ START=93
|
||||
USE_PROCD=1
|
||||
|
||||
get_config() {
|
||||
config_get_bool enabled "$1" enabled 1
|
||||
config_get_bool enabled "$1" enabled 0
|
||||
config_get data_dir "$1" data_dir ""
|
||||
config_get listen_mode "$1" listen_mode "auto"
|
||||
config_get_bool external_port_enabled "$1" external_port_enabled 0
|
||||
config_get socket_path "$1" socket_path "/var/run/kaiplus.sock"
|
||||
config_get port "$1" port "8189"
|
||||
config_get bind_addr "$1" bind_addr "0.0.0.0"
|
||||
config_get base_path "$1" base_path "/apps/kaiplus/"
|
||||
config_get system_role "$1" system_role "istoreos"
|
||||
}
|
||||
@@ -50,15 +54,35 @@ start_service() {
|
||||
|
||||
logger -t kaiplus "Starting KaiPlus service"
|
||||
procd_open_instance kaiplus_bin
|
||||
procd_set_param env \
|
||||
KAIPLUS_HOME="$data_dir" \
|
||||
KAIPLUS_SYSTEM_ROLE="$system_role" \
|
||||
KAIPLUS_WORKSPACE_HELPER_DIR="/usr/share/kaiplus/helpers" \
|
||||
KAIPLUS_WORKSPACE_TOOL_INSTALL_DIR="/tmp/kaiplus_workspace_tool" \
|
||||
KAIPLUS_SSH_HELPER_TOKEN="$helper_token" \
|
||||
REASONIX_CREDENTIALS_STORE="file"
|
||||
if [ "$listen_mode" = "tcp" ] || [ "$external_port_enabled" = 1 ]; then
|
||||
procd_set_param env \
|
||||
KAIPLUS_HOME="$data_dir" \
|
||||
KAIPLUS_SYSTEM_ROLE="$system_role" \
|
||||
KAIPLUS_LISTEN_MODE="tcp" \
|
||||
KAIPLUS_WORKSPACE_HELPER_DIR="/usr/share/kaiplus/helpers" \
|
||||
KAIPLUS_WORKSPACE_TOOL_INSTALL_DIR="/tmp/kaiplus_workspace_tool" \
|
||||
KAIPLUS_SSH_HELPER_TOKEN="$helper_token" \
|
||||
REASONIX_CREDENTIALS_STORE="file"
|
||||
else
|
||||
mkdir_p "$(dirname "$socket_path")"
|
||||
procd_set_param env \
|
||||
KAIPLUS_HOME="$data_dir" \
|
||||
KAIPLUS_SYSTEM_ROLE="$system_role" \
|
||||
KAIPLUS_LISTEN_MODE="unix" \
|
||||
KAIPLUS_SOCKET_PATH="$socket_path" \
|
||||
KAIPLUS_WORKSPACE_HELPER_DIR="/usr/share/kaiplus/helpers" \
|
||||
KAIPLUS_WORKSPACE_TOOL_INSTALL_DIR="/tmp/kaiplus_workspace_tool" \
|
||||
KAIPLUS_SSH_HELPER_TOKEN="$helper_token" \
|
||||
REASONIX_CREDENTIALS_STORE="file"
|
||||
fi
|
||||
procd_set_param command /usr/sbin/kaiplus_bin kaiplus-web
|
||||
procd_append_param command --addr "0.0.0.0:$port"
|
||||
if [ "$listen_mode" = "tcp" ] || [ "$external_port_enabled" = 1 ]; then
|
||||
procd_append_param command --listen-mode tcp
|
||||
procd_append_param command --addr "$bind_addr:$port"
|
||||
else
|
||||
procd_append_param command --listen-mode unix
|
||||
procd_append_param command --socket-path "$socket_path"
|
||||
fi
|
||||
procd_append_param command --data-dir "$data_dir"
|
||||
procd_append_param command --static-dir /usr/share/kaiplus/www
|
||||
procd_append_param command --defaults-dir /usr/share/kaiplus/defaults
|
||||
|
||||
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkease-common-bin
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=1.7.5
|
||||
PKG_RELEASE:=19
|
||||
PKG_RELEASE:=20
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=ff87526d94aaa464981d5111c8631ca66396bef7
|
||||
PKG_SOURCE_VERSION:=be8ef471de3f6385bf2d570bd4048328361c24c0
|
||||
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_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
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=1.7.5
|
||||
PKG_RELEASE:=22
|
||||
PKG_RELEASE:=23
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=ff87526d94aaa464981d5111c8631ca66396bef7
|
||||
PKG_SOURCE_VERSION:=be8ef471de3f6385bf2d570bd4048328361c24c0
|
||||
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_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||
|
||||
@@ -10,8 +10,8 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
|
||||
PKG_NAME:=linkeasefull
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=3.0.13
|
||||
PKG_RELEASE:=21
|
||||
PKG_SOURCE_DATE:=3.0.16
|
||||
PKG_RELEASE:=22
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -24,7 +24,7 @@ LINKEASE_RUNTIME_ARCH:=arm64
|
||||
PKG_HASH:=skip
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=ff87526d94aaa464981d5111c8631ca66396bef7
|
||||
PKG_SOURCE_VERSION:=be8ef471de3f6385bf2d570bd4048328361c24c0
|
||||
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_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH)
|
||||
|
||||
@@ -110,6 +110,7 @@ start_service() {
|
||||
--localApi "$LOCAL_API" \
|
||||
--supplierCode "linkease"
|
||||
procd_set_param env \
|
||||
"LINKEASE_AUTH_PROVIDER=openwrt" \
|
||||
"KAIPLUS_ENABLED=0"
|
||||
procd_set_param respawn
|
||||
procd_set_param stdout 1
|
||||
|
||||
@@ -11,7 +11,7 @@ LUCI_DEPENDS:=+dockermanager +luci-lib-linkeaseauth
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.1.1
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ local function uhttpd_apps_proxy_available()
|
||||
return uhttpd_supports_proxy_prefix() and uhttpd_has_apps_proxy_prefix()
|
||||
end
|
||||
|
||||
local function linkeasefull_running()
|
||||
local sys = require "luci.sys"
|
||||
return sys.call("[ -x /etc/init.d/linkeasefull ] && /etc/init.d/linkeasefull running >/dev/null 2>&1") == 0
|
||||
end
|
||||
|
||||
local function normalized_base_path(path)
|
||||
path = path or "/apps/dockermanager/"
|
||||
if path:sub(1, 1) ~= "/" then
|
||||
@@ -94,29 +99,79 @@ local function dockermanager_config()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local port = uci:get_first("dockermanager", "dockermanager", "port") or "8192"
|
||||
local base_path = normalized_base_path(uci:get_first("dockermanager", "dockermanager", "base_path"))
|
||||
return port, base_path
|
||||
local external_port_enabled = uci:get_first("dockermanager", "dockermanager", "external_port_enabled") == "1"
|
||||
local listen_mode = uci:get_first("dockermanager", "dockermanager", "listen_mode") or "auto"
|
||||
local socket_path = uci:get_first("dockermanager", "dockermanager", "socket_path") or "/var/run/dockermanager.sock"
|
||||
return port, base_path, external_port_enabled, listen_mode, socket_path
|
||||
end
|
||||
|
||||
local function dockermanager_entry_url()
|
||||
local port, base_path = dockermanager_config()
|
||||
if uhttpd_apps_proxy_available() then
|
||||
local port, base_path, external_port_enabled = dockermanager_config()
|
||||
if linkeasefull_running() and uhttpd_apps_proxy_available() then
|
||||
return base_path
|
||||
end
|
||||
if not external_port_enabled then
|
||||
return nil
|
||||
end
|
||||
return "http://" .. url_authority(request_or_lan_host(), port) .. base_path
|
||||
end
|
||||
|
||||
local function enable_external_port()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local section = nil
|
||||
uci:foreach("dockermanager", "dockermanager", function(s)
|
||||
if not section then
|
||||
section = s[".name"]
|
||||
end
|
||||
end)
|
||||
if section then
|
||||
uci:set("dockermanager", section, "external_port_enabled", "1")
|
||||
uci:set("dockermanager", section, "listen_mode", "tcp")
|
||||
uci:commit("dockermanager")
|
||||
end
|
||||
local sys = require "luci.sys"
|
||||
sys.call("/etc/init.d/dockermanager restart >/dev/null 2>&1; sleep 1")
|
||||
end
|
||||
|
||||
local function render_enable_port_confirm()
|
||||
local dispatcher = require "luci.dispatcher"
|
||||
local confirm_url = dispatcher.build_url("admin", "services", "dockermanager", "open") .. "?enable_port=1"
|
||||
luci.http.prepare_content("text/html; charset=utf-8")
|
||||
luci.http.write("<!doctype html><html><head><meta charset='utf-8'><title>Docker Manager</title></head><body>")
|
||||
luci.http.write("<h2>Docker Manager</h2>")
|
||||
luci.http.write("<p>当前系统无法通过 LinkEase 桌面代理打开 Docker Manager。</p>")
|
||||
luci.http.write("<p>可以启用局域网端口 8192 后继续打开。</p>")
|
||||
luci.http.write("<p><a class='cbi-button cbi-button-apply' href='" .. confirm_url .. "'>启用 8192 并打开</a></p>")
|
||||
luci.http.write("</body></html>")
|
||||
end
|
||||
|
||||
local function render_open_failed()
|
||||
luci.http.prepare_content("text/html; charset=utf-8")
|
||||
luci.http.write("<!doctype html><html><head><meta charset='utf-8'><title>Docker Manager</title></head><body>")
|
||||
luci.http.write("<h2>Docker Manager</h2>")
|
||||
luci.http.write("<p>Docker Manager 已安装,但当前没有可用访问入口。</p>")
|
||||
luci.http.write("<p>请启用 LinkEaseFull 的 /apps 代理,或在 Docker Manager 设置中开放 8192 端口。</p>")
|
||||
luci.http.write("</body></html>")
|
||||
end
|
||||
|
||||
function dockermanager_status()
|
||||
local sys = require "luci.sys"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local port, base_path = dockermanager_config()
|
||||
local port, base_path, external_port_enabled, listen_mode, socket_path = dockermanager_config()
|
||||
local entry_url = dockermanager_entry_url()
|
||||
|
||||
local status = {
|
||||
running = (sys.call("pidof docker-manager >/dev/null") == 0),
|
||||
port = port,
|
||||
base_path = base_path,
|
||||
listen_mode = listen_mode,
|
||||
socket_path = socket_path,
|
||||
external_port_enabled = external_port_enabled,
|
||||
entry_url = entry_url or "",
|
||||
lan_ip = uci:get("network", "lan", "ipaddr") or "",
|
||||
proxy_prefix_supported = uhttpd_supports_proxy_prefix(),
|
||||
proxy_prefix_enabled = uhttpd_apps_proxy_available()
|
||||
proxy_prefix_enabled = uhttpd_apps_proxy_available(),
|
||||
linkeasefull_running = linkeasefull_running()
|
||||
}
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
@@ -125,5 +180,19 @@ end
|
||||
|
||||
function dockermanager_open()
|
||||
local http = require "luci.http"
|
||||
http.redirect(linkease_auth_url("auth") .. "?return=" .. cookie_encode(dockermanager_entry_url()))
|
||||
if http.formvalue("enable_port") == "1" then
|
||||
enable_external_port()
|
||||
end
|
||||
|
||||
local entry_url = dockermanager_entry_url()
|
||||
if entry_url then
|
||||
http.redirect(linkease_auth_url("auth") .. "?return=" .. cookie_encode(entry_url))
|
||||
return
|
||||
end
|
||||
|
||||
if http.formvalue("enable_port") == "1" then
|
||||
render_open_failed()
|
||||
return
|
||||
end
|
||||
render_enable_port_confirm()
|
||||
end
|
||||
|
||||
@@ -12,6 +12,22 @@ s:option(Flag, "enabled", translate("Enable")).rmempty = false
|
||||
local data_dir = s:option(Value, "data_dir", translate("Data directory"))
|
||||
data_dir.rmempty = false
|
||||
|
||||
local listen_mode = s:option(ListValue, "listen_mode", translate("Listen mode"))
|
||||
listen_mode:value("auto", translate("Auto"))
|
||||
listen_mode:value("unix", translate("Unix socket"))
|
||||
listen_mode:value("tcp", translate("TCP port"))
|
||||
listen_mode.default = "auto"
|
||||
listen_mode.rmempty = false
|
||||
|
||||
local external_port_enabled = s:option(Flag, "external_port_enabled", translate("Enable external port access"))
|
||||
external_port_enabled.default = "0"
|
||||
external_port_enabled.rmempty = false
|
||||
|
||||
local socket_path = s:option(Value, "socket_path", translate("Unix socket path"))
|
||||
socket_path.default = "/var/run/dockermanager.sock"
|
||||
socket_path.rmempty = false
|
||||
socket_path.readonly = true
|
||||
|
||||
local port = s:option(Value, "port", translate("Listen port"))
|
||||
port.default = "8192"
|
||||
port.rmempty = false
|
||||
@@ -24,5 +40,6 @@ bind_addr.rmempty = false
|
||||
local base_path = s:option(Value, "base_path", translate("Base path"))
|
||||
base_path.default = "/apps/dockermanager/"
|
||||
base_path.rmempty = false
|
||||
base_path.readonly = true
|
||||
|
||||
return m
|
||||
|
||||
@@ -5,10 +5,22 @@
|
||||
var tb = document.getElementById('dockermanager_status');
|
||||
if (st && tb)
|
||||
{
|
||||
var status = st.running
|
||||
? '<br/><em style=\"color:green\"><%:The Docker Manager service is running.%></em>'
|
||||
: '<br/><em style=\"color:red\"><%:The Docker Manager service is not running.%></em>';
|
||||
tb.innerHTML = status;
|
||||
var status = st.running
|
||||
? '<br/><em style=\"color:green\"><%:The Docker Manager service is running.%></em>'
|
||||
: '<br/><em style=\"color:red\"><%:The Docker Manager service is not running.%></em>';
|
||||
if (st.proxy_prefix_enabled && st.linkeasefull_running)
|
||||
{
|
||||
status = status + '<br/><em><%:Access mode: LinkEase Desktop proxy.%></em>';
|
||||
}
|
||||
else if (st.external_port_enabled)
|
||||
{
|
||||
status = status + '<br/><em><%:Access mode: external port.%></em>';
|
||||
}
|
||||
else
|
||||
{
|
||||
status = status + '<br/><em><%:External port access is disabled.%></em>';
|
||||
}
|
||||
tb.innerHTML = status;
|
||||
|
||||
if (st.running)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +logd +jsonfilter
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.1.37
|
||||
PKG_RELEASE:=3
|
||||
PKG_RELEASE:=4
|
||||
|
||||
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
||||
ifdef SOURCE_DATE_EPOCH
|
||||
|
||||
@@ -503,7 +503,6 @@ return view.extend({
|
||||
this.lastRulesError = 'rules_unavailable';
|
||||
}
|
||||
this.updateBackendUi();
|
||||
this.updateStatus();
|
||||
},
|
||||
|
||||
backendDisplayLabel() {
|
||||
@@ -520,8 +519,20 @@ return view.extend({
|
||||
map.setAttribute('data-backend', this.firewallBackend || 'unknown');
|
||||
|
||||
const label = document.getElementById('fwlive-backend');
|
||||
if (label)
|
||||
label.textContent = this.backendDisplayLabel();
|
||||
if (label) {
|
||||
let text = this.backendDisplayLabel();
|
||||
if (this.lastRulesError) {
|
||||
let err = '';
|
||||
if (this.lastRulesError === 'rules_truncated')
|
||||
err = _('Rule labels incomplete — map truncated');
|
||||
else if (this.lastRulesError === 'mktemp_failed')
|
||||
err = _('Rule labels unavailable — temp file failed');
|
||||
else
|
||||
err = _('Rule labels unavailable');
|
||||
text = text ? text + ' \u00b7 ' + err : err;
|
||||
}
|
||||
label.textContent = text;
|
||||
}
|
||||
|
||||
this.updateEmptyStateUi();
|
||||
},
|
||||
@@ -928,17 +939,6 @@ return view.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lastRulesError) {
|
||||
status.className = 'fwlive-status fwlive-status-error';
|
||||
if (this.lastRulesError === 'rules_truncated')
|
||||
status.textContent = _('Rule labels incomplete — map truncated') + suffix;
|
||||
else if (this.lastRulesError === 'mktemp_failed')
|
||||
status.textContent = _('Rule labels unavailable — temp file failed') + suffix;
|
||||
else
|
||||
status.textContent = _('Rule labels unavailable') + suffix;
|
||||
return;
|
||||
}
|
||||
|
||||
status.className = this.paused
|
||||
? 'fwlive-status fwlive-status-paused'
|
||||
: 'fwlive-status';
|
||||
|
||||
@@ -382,6 +382,8 @@ collect_logging_blockers() {
|
||||
[ -n "$zone" ] || logging_blockers_append 'no_wan_zone'
|
||||
check_nf_log_ipv4 || logging_blockers_append 'nf_log_ipv4_missing'
|
||||
check_nf_log_ipv6 || logging_blockers_append 'nf_log_ipv6_missing'
|
||||
# rpcd/fwlive run_with_timeout fail-closes to 127 without timeout (#229): surface as blocker.
|
||||
command -v timeout >/dev/null 2>&1 || logging_blockers_append 'timeout_missing'
|
||||
|
||||
[ -n "$LOGGING_BLOCKERS" ] || return 0
|
||||
return 1
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=KaiPlus
|
||||
PKG_VERSION:=1.0.1
|
||||
PKG_RELEASE:=5
|
||||
PKG_RELEASE:=6
|
||||
LUCI_DEPENDS:=+kaiplus +luci-compat
|
||||
LUCI_MINIFY_CSS:=0
|
||||
LUCI_MINIFY_JS:=0
|
||||
|
||||
@@ -1,27 +1,197 @@
|
||||
local http = require "luci.http"
|
||||
|
||||
module("luci.controller.kaiplus", package.seeall)
|
||||
|
||||
local APPS_PROXY_PREFIX = "/apps=http://127.0.0.1:19290"
|
||||
|
||||
function index()
|
||||
entry({"admin", "services", "kaiplus_status"}, call("kaiplus_status"))
|
||||
local open = entry({"admin", "services", "kaiplus", "open"}, call("kaiplus_open"))
|
||||
open.leaf = true
|
||||
open.dependent = false
|
||||
open.sysauth = "root"
|
||||
open.sysauth_authenticator = "htmlauth"
|
||||
|
||||
if not nixio.fs.access("/etc/config/kaiplus") then
|
||||
return
|
||||
end
|
||||
local page
|
||||
page = entry({"admin", "services", "kaiplus"}, cbi("kaiplus"), _("KaiPlus"), 100)
|
||||
page.dependent = true
|
||||
entry({"admin", "services", "kaiplus_status"}, call("kaiplus_status"))
|
||||
end
|
||||
|
||||
local function uhttpd_has_apps_proxy_prefix()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local mappings = uci:get_list("uhttpd", "main", "proxy_prefix") or {}
|
||||
|
||||
for _, mapping in ipairs(mappings) do
|
||||
if mapping == APPS_PROXY_PREFIX then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function uhttpd_supports_proxy_prefix()
|
||||
local sys = require "luci.sys"
|
||||
return sys.call("grep -qr 'proxy_prefix' /etc/init.d/uhttpd /lib/functions /usr/share/uhttpd 2>/dev/null") == 0
|
||||
end
|
||||
|
||||
local function uhttpd_apps_proxy_available()
|
||||
return uhttpd_supports_proxy_prefix() and uhttpd_has_apps_proxy_prefix()
|
||||
end
|
||||
|
||||
local function linkeasefull_running()
|
||||
local sys = require "luci.sys"
|
||||
return sys.call("[ -x /etc/init.d/linkeasefull ] && /etc/init.d/linkeasefull running >/dev/null 2>&1") == 0
|
||||
end
|
||||
|
||||
local function normalized_base_path(path)
|
||||
path = path or "/apps/kaiplus/"
|
||||
if path:sub(1, 1) ~= "/" then
|
||||
path = "/" .. path
|
||||
end
|
||||
if path:sub(-1) ~= "/" then
|
||||
path = path .. "/"
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
local function cookie_encode(value)
|
||||
return tostring(value or ""):gsub("([^A-Za-z0-9._~-])", function(char)
|
||||
return string.format("%%%02X", char:byte())
|
||||
end)
|
||||
end
|
||||
|
||||
local function authority_host(authority)
|
||||
if not authority or authority == "" then
|
||||
return ""
|
||||
end
|
||||
if authority:sub(1, 1) == "[" then
|
||||
return authority:match("^%[([^%]]+)%]") or ""
|
||||
end
|
||||
return authority:match("^([^:]+)") or authority
|
||||
end
|
||||
|
||||
local function url_authority(host, port)
|
||||
if not host or host == "" then
|
||||
host = "127.0.0.1"
|
||||
end
|
||||
if host:find(":") and host:sub(1, 1) ~= "[" then
|
||||
host = "[" .. host .. "]"
|
||||
end
|
||||
return host .. ":" .. tostring(port)
|
||||
end
|
||||
|
||||
local function request_or_lan_host()
|
||||
local http = require "luci.http"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local host = authority_host(http.getenv("HTTP_HOST") or "")
|
||||
if host ~= "" then
|
||||
return host
|
||||
end
|
||||
return uci:get("network", "lan", "ipaddr") or "127.0.0.1"
|
||||
end
|
||||
|
||||
local function linkease_auth_url(name)
|
||||
local dispatcher = require "luci.dispatcher"
|
||||
return dispatcher.build_url("admin", "services", "linkease_auth", name)
|
||||
end
|
||||
|
||||
local function kaiplus_config()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local port = uci:get_first("kaiplus", "kaiplus", "port", "8189")
|
||||
local base_path = normalized_base_path(uci:get_first("kaiplus", "kaiplus", "base_path"))
|
||||
local external_port_enabled = uci:get_first("kaiplus", "kaiplus", "external_port_enabled") == "1"
|
||||
local listen_mode = uci:get_first("kaiplus", "kaiplus", "listen_mode") or "auto"
|
||||
local socket_path = uci:get_first("kaiplus", "kaiplus", "socket_path") or "/var/run/kaiplus.sock"
|
||||
return port, base_path, external_port_enabled, listen_mode, socket_path
|
||||
end
|
||||
|
||||
local function kaiplus_entry_url()
|
||||
local port, base_path, external_port_enabled = kaiplus_config()
|
||||
if linkeasefull_running() and uhttpd_apps_proxy_available() then
|
||||
return base_path
|
||||
end
|
||||
if not external_port_enabled then
|
||||
return nil
|
||||
end
|
||||
return "http://" .. url_authority(request_or_lan_host(), port) .. base_path
|
||||
end
|
||||
|
||||
local function enable_external_port()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local section = nil
|
||||
uci:foreach("kaiplus", "kaiplus", function(s)
|
||||
if not section then
|
||||
section = s[".name"]
|
||||
end
|
||||
end)
|
||||
if section then
|
||||
uci:set("kaiplus", section, "external_port_enabled", "1")
|
||||
uci:set("kaiplus", section, "listen_mode", "tcp")
|
||||
uci:commit("kaiplus")
|
||||
end
|
||||
local sys = require "luci.sys"
|
||||
sys.call("/etc/init.d/kaiplus restart >/dev/null 2>&1; sleep 1")
|
||||
end
|
||||
|
||||
local function render_enable_port_confirm()
|
||||
local dispatcher = require "luci.dispatcher"
|
||||
local confirm_url = dispatcher.build_url("admin", "services", "kaiplus", "open") .. "?enable_port=1"
|
||||
luci.http.prepare_content("text/html; charset=utf-8")
|
||||
luci.http.write("<!doctype html><html><head><meta charset='utf-8'><title>KaiPlus</title></head><body>")
|
||||
luci.http.write("<h2>KaiPlus</h2>")
|
||||
luci.http.write("<p>当前系统无法通过 LinkEase 桌面代理打开 KaiPlus。</p>")
|
||||
luci.http.write("<p>可以启用局域网端口 8189 后继续打开。</p>")
|
||||
luci.http.write("<p><a class='cbi-button cbi-button-apply' href='" .. confirm_url .. "'>启用 8189 并打开</a></p>")
|
||||
luci.http.write("</body></html>")
|
||||
end
|
||||
|
||||
local function render_open_failed()
|
||||
luci.http.prepare_content("text/html; charset=utf-8")
|
||||
luci.http.write("<!doctype html><html><head><meta charset='utf-8'><title>KaiPlus</title></head><body>")
|
||||
luci.http.write("<h2>KaiPlus</h2>")
|
||||
luci.http.write("<p>KaiPlus 已安装,但当前没有可用访问入口。</p>")
|
||||
luci.http.write("<p>请启用 LinkEaseFull 的 /apps 代理,或在 KaiPlus 设置中开放 8189 端口。</p>")
|
||||
luci.http.write("</body></html>")
|
||||
end
|
||||
|
||||
function kaiplus_status()
|
||||
local sys = require "luci.sys"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local port = uci:get_first("kaiplus", "kaiplus", "port", "8189")
|
||||
local base_path = uci:get_first("kaiplus", "kaiplus", "base_path", "/apps/kaiplus/")
|
||||
local port, base_path, external_port_enabled, listen_mode, socket_path = kaiplus_config()
|
||||
local entry_url = kaiplus_entry_url()
|
||||
local status = {
|
||||
running = (sys.call("pidof kaiplus_bin >/dev/null") == 0),
|
||||
port = port,
|
||||
base_path = base_path
|
||||
base_path = base_path,
|
||||
listen_mode = listen_mode,
|
||||
socket_path = socket_path,
|
||||
external_port_enabled = external_port_enabled,
|
||||
entry_url = entry_url or "",
|
||||
lan_ip = uci:get("network", "lan", "ipaddr") or "",
|
||||
proxy_prefix_supported = uhttpd_supports_proxy_prefix(),
|
||||
proxy_prefix_enabled = uhttpd_apps_proxy_available(),
|
||||
linkeasefull_running = linkeasefull_running()
|
||||
}
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(status)
|
||||
end
|
||||
|
||||
function kaiplus_open()
|
||||
local http = require "luci.http"
|
||||
if http.formvalue("enable_port") == "1" then
|
||||
enable_external_port()
|
||||
end
|
||||
|
||||
local entry_url = kaiplus_entry_url()
|
||||
if entry_url then
|
||||
http.redirect(linkease_auth_url("auth") .. "?return=" .. cookie_encode(entry_url))
|
||||
return
|
||||
end
|
||||
|
||||
if http.formvalue("enable_port") == "1" then
|
||||
render_open_failed()
|
||||
return
|
||||
end
|
||||
render_enable_port_confirm()
|
||||
end
|
||||
|
||||
@@ -31,10 +31,35 @@ for _, val in pairs(paths) do
|
||||
end
|
||||
data_dir.default = default_path
|
||||
|
||||
local listen_mode = s:option(ListValue, "listen_mode", translate("Listen mode"))
|
||||
listen_mode:value("auto", translate("Auto"))
|
||||
listen_mode:value("unix", translate("Unix socket"))
|
||||
listen_mode:value("tcp", translate("TCP port"))
|
||||
listen_mode.default = "auto"
|
||||
listen_mode.rmempty = false
|
||||
|
||||
local external_port_enabled = s:option(Flag, "external_port_enabled", translate("Enable external port access"))
|
||||
external_port_enabled.default = "0"
|
||||
external_port_enabled.rmempty = false
|
||||
|
||||
local socket_path = s:option(Value, "socket_path", translate("Unix socket path"))
|
||||
socket_path.default = "/var/run/kaiplus.sock"
|
||||
socket_path.rmempty = false
|
||||
socket_path.readonly = true
|
||||
|
||||
local port = s:option(Value, "port", translate("Web port"))
|
||||
port.default = "8189"
|
||||
port.rmempty = false
|
||||
port.datatype = "port"
|
||||
port.description = translate("Port for the KaiPlus web service.")
|
||||
|
||||
local bind_addr = s:option(Value, "bind_addr", translate("Bind address"))
|
||||
bind_addr.default = "0.0.0.0"
|
||||
bind_addr.rmempty = false
|
||||
|
||||
local base_path = s:option(Value, "base_path", translate("Base path"))
|
||||
base_path.default = "/apps/kaiplus/"
|
||||
base_path.rmempty = false
|
||||
base_path.readonly = true
|
||||
|
||||
return m
|
||||
|
||||
@@ -2,19 +2,22 @@
|
||||
XHR.poll(5, '<%=url("admin/services/kaiplus_status")%>', null, function(x, st) {
|
||||
var el = document.getElementById('kaiplus_status');
|
||||
if (st && el) {
|
||||
if (!st.running) {
|
||||
el.innerHTML = '<br/><em style=\"color:red\"><%:The KaiPlus service is not running.%></em>';
|
||||
var status = st.running
|
||||
? '<br/><em style=\"color:green\"><%:The KaiPlus service is running.%></em>'
|
||||
: '<br/><em style=\"color:red\"><%:The KaiPlus service is not running.%></em>';
|
||||
if (st.proxy_prefix_enabled && st.linkeasefull_running) {
|
||||
status = status + '<br/><em><%:Access mode: LinkEase Desktop proxy.%></em>';
|
||||
}
|
||||
else if (st.external_port_enabled) {
|
||||
status = status + '<br/><em><%:Access mode: external port.%></em>';
|
||||
}
|
||||
else {
|
||||
status = status + '<br/><em><%:External port access is disabled.%></em>';
|
||||
}
|
||||
el.innerHTML = status;
|
||||
if (st.running) {
|
||||
var basePath = st.base_path || "/apps/kaiplus/";
|
||||
if (basePath.charAt(0) !== "/") {
|
||||
basePath = "/" + basePath;
|
||||
}
|
||||
if (basePath.charAt(basePath.length - 1) !== "/") {
|
||||
basePath = basePath + "/";
|
||||
}
|
||||
el.innerHTML = '<br/><em style=\"color:green\"><%:The KaiPlus service is running.%></em>'
|
||||
+ "<br/><br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open KaiPlus%> \" onclick=\"window.open('http://' + window.location.hostname + ':' + st.port + basePath)\"/>";
|
||||
el.innerHTML = el.innerHTML
|
||||
+ "<br/><br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open KaiPlus%> \" onclick=\"window.open('<%=url("admin/services/kaiplus/open")%>')\"/>";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ LUCI_TITLE:=LuCI support for quickstart
|
||||
LUCI_DEPENDS:=+quickstart +luci-app-ttyd +luci-i18n-ttyd-zh-cn
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.12.8-r1
|
||||
PKG_VERSION:=0.12.10-r1
|
||||
# PKG_RELEASE MUST be empty for luci.mk
|
||||
PKG_RELEASE:=
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,5 @@
|
||||
local http = require "luci.http"
|
||||
local string = require "string"
|
||||
|
||||
module("luci.controller.quickstart", package.seeall)
|
||||
|
||||
@@ -25,6 +26,9 @@ function index()
|
||||
else
|
||||
entry({"admin", "quickstart"}, call("redirect_fallback")).leaf = true
|
||||
end
|
||||
if not nixio.fs.access("/usr/share/luci/menu.d/luci-app-dockerman.json") then
|
||||
entry({"admin", "services", "dockerman"}, call("redirect_dockerman")).leaf = true
|
||||
end
|
||||
end
|
||||
|
||||
function networkguide_index()
|
||||
@@ -39,6 +43,24 @@ function redirect_fallback()
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "status"))
|
||||
end
|
||||
|
||||
local function starts_with(str, start)
|
||||
return string.sub(str, 1, #start) == start
|
||||
end
|
||||
|
||||
local function replace_first(str, pattern, replacement)
|
||||
-- The third argument (1) limits the replacement to the first match
|
||||
local result = str:gsub(pattern, replacement, 1)
|
||||
return result
|
||||
end
|
||||
|
||||
function redirect_dockerman(path, ...)
|
||||
local newargs = {}
|
||||
if path ~= nil then
|
||||
newargs = {path == "configuration" and "config" or path, ...}
|
||||
end
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "docker", unpack(newargs)))
|
||||
end
|
||||
|
||||
local function vue_lang()
|
||||
local i18n = require("luci.i18n")
|
||||
local lang = i18n.translate("quickstart_vue_lang")
|
||||
|
||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
||||
FOOTSTRAP_VERSION?=
|
||||
ifneq ($(FOOTSTRAP_VERSION),)
|
||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||
PKG_RELEASE:=50
|
||||
PKG_RELEASE:=51
|
||||
endif
|
||||
|
||||
LUCI_TITLE:=Footstrap Theme
|
||||
|
||||
@@ -110,6 +110,13 @@ function run() {
|
||||
* question from which box can carry a floor. */
|
||||
const SHRINKS = '.cbi-section > div, .table';
|
||||
|
||||
/* A box qualifies for a floor through its CHILDREN, so emptying it takes it out of `SHRINKS` — and
|
||||
* a floor nothing sweeps any more is a floor nothing can take off: measured on Network ->
|
||||
* Interfaces, a section emptied of its table kept 1299px of `min-height` for the life of the page.
|
||||
* Every floor this writes is marked, so the sweep finds its own work again whatever became of the
|
||||
* markup underneath. The attribute is in the theme's own namespace and says nothing to CSS. */
|
||||
const FLOORED = '[data-fs-floor]';
|
||||
|
||||
/* What the container would stand at with no floor under it, asked of the CONTENT rather than by
|
||||
* taking the floor off and re-measuring.
|
||||
*
|
||||
@@ -144,22 +151,27 @@ function naturalHeight(el) {
|
||||
if (!box.height) return 0;
|
||||
const end = last.getBoundingClientRect();
|
||||
if (!end.height && !end.width) return 0; /* a last child out of the flow says nothing */
|
||||
let bottom = end.bottom;
|
||||
/* AND THE TEXT AFTER IT. A box whose content ends in text ends below its last ELEMENT, and
|
||||
* measuring to that element writes a floor shorter than the box: `.cbi-section-descr` on
|
||||
* /admin/network/dhcp measured 115px against the 156px it stands at, and a floor 41px short lets
|
||||
* the document shrink under the reader during the very tick the floor is there to hold.
|
||||
*
|
||||
* Only the tail, not `selectNodeContents(el)`: a Range over the whole box takes in whatever is
|
||||
* out of the flow inside it and writes a floor TALLER than the box instead — 205px against 107
|
||||
* on the Overview, which is the same blank page seen from the other side. */
|
||||
if (last.nextSibling) {
|
||||
const tail = document.createRange();
|
||||
tail.setStartAfter(last);
|
||||
tail.setEnd(el, el.childNodes.length);
|
||||
const box2 = tail.getBoundingClientRect();
|
||||
if (box2.height || box2.width) bottom = Math.max(bottom, box2.bottom);
|
||||
}
|
||||
const cs = window.getComputedStyle(el);
|
||||
const below = parseFloat(cs.paddingBottom) + parseFloat(cs.borderBottomWidth);
|
||||
return Math.round(end.bottom - box.top + (below || 0));
|
||||
return Math.round(bottom - box.top + (below || 0));
|
||||
}
|
||||
|
||||
/* The floor is the height the next tick may not go below, one per container. Read in one pass and
|
||||
* written in another, so the sweep costs a single forced layout rather than one per element.
|
||||
*
|
||||
* WRITTEN ONLY WHERE THE VALUE CHANGES, which is the difference between a floor and a page the
|
||||
* engine refuses to anchor: see naturalHeight() above. Measured over 25 s of real polling on the
|
||||
* Overview at 390px, the clear-and-rewrite shape wrote style 1550 times on 25.12 and 170 times on
|
||||
* ImmortalWrt 24.10, of which 75 and 62 carried a value that had actually moved — the rest were
|
||||
* suppression bought for nothing.
|
||||
*
|
||||
* Not while the reader scrolls: a rect read is a forced layout, and a floor staying where it was is
|
||||
* still a floor. */
|
||||
/* The floor is the height the next tick may not go below, one per box. Read in one pass and written
|
||||
* in another, so the sweep costs a single forced layout rather than one per element.
|
||||
*
|
||||
@@ -179,26 +191,85 @@ function naturalHeight(el) {
|
||||
*
|
||||
* Not while the reader scrolls: a rect read is a forced layout, and a floor staying where it was is
|
||||
* still a floor. */
|
||||
/* Boxes that measured empty on the LAST pass, so a floor that is holding nothing up can be told
|
||||
* from one that is holding the page still while `dom.content()` refills. WeakSet: a box the router
|
||||
* has replaced is garbage, and this must not be what keeps it alive. */
|
||||
const emptied = new WeakSet();
|
||||
|
||||
/* Off in one place, so the mark and the style can never disagree about who wears a floor. */
|
||||
function dropFloor(box) {
|
||||
if (box.style.minHeight) box.style.minHeight = '';
|
||||
box.removeAttribute('data-fs-floor');
|
||||
}
|
||||
|
||||
/* THE SECOND LOOK HAS TO BE SCHEDULED, not waited for. Every other pass here is driven by the
|
||||
* MutationObserver on `#view`, and a container that empties and stays empty produces no further
|
||||
* mutation — so "clear it if it is still empty next pass" never gets a next pass, and the floor
|
||||
* stands for the life of the page (measured: 1299px on Network -> Interfaces).
|
||||
*
|
||||
* One poll interval, because that is how long a container that is genuinely refilling may take: the
|
||||
* router's own `pollinterval` when the page will say it, and its shipped default of 5 s otherwise.
|
||||
* Shorter risks taking the floor away from a tick still in flight, which is the 568px clamp this
|
||||
* whole mechanism exists to stop. */
|
||||
let _emptyCheck = null;
|
||||
|
||||
function checkEmptyLater() {
|
||||
if (_emptyCheck) return;
|
||||
let secs = 5;
|
||||
try { secs = (window.L && L.env && L.env.pollinterval) || 5; } catch (e) { /* not on a LuCI page */ }
|
||||
_emptyCheck = setTimeout(() => { _emptyCheck = null; run(); }, secs * 1000);
|
||||
}
|
||||
|
||||
function holdFloor() {
|
||||
if (scrolling()) return;
|
||||
const host = document.getElementById('view');
|
||||
if (!host) return; /* the login page has no view */
|
||||
const boxes = [], hs = [];
|
||||
host.querySelectorAll(SHRINKS).forEach((el) => {
|
||||
let box = el;
|
||||
while (box && box !== host && window.getComputedStyle(box).display.startsWith('table'))
|
||||
const boxes = [], hs = [], gone = [];
|
||||
host.querySelectorAll(SHRINKS + ', ' + FLOORED).forEach((el) => {
|
||||
let box = el, cs = window.getComputedStyle(el);
|
||||
while (box && box !== host && cs.display.startsWith('table')) {
|
||||
box = box.parentElement;
|
||||
if (box) cs = window.getComputedStyle(box);
|
||||
}
|
||||
/* several tables in one section climb to the same box; it needs one floor, not one each */
|
||||
if (!box || box === host || boxes.indexOf(box) !== -1) return;
|
||||
boxes.push(box);
|
||||
hs.push(naturalHeight(box));
|
||||
/* `visibility` inherits, so this is equally true of every box inside a collapsed pane */
|
||||
const hidden = cs.visibility === 'hidden';
|
||||
gone.push(hidden);
|
||||
hs.push(hidden ? 0 : naturalHeight(box));
|
||||
});
|
||||
boxes.forEach((box, i) => {
|
||||
/* zero is an empty box, and the floor it already wears is what holds it up — the moment this
|
||||
* whole mechanism exists for */
|
||||
if (hs[i] <= 0) return;
|
||||
/* A BOX THE READER CANNOT SEE GIVES ITS FLOOR BACK. `min-height` beats the `height: 0` an
|
||||
* inactive tab pane is collapsed with (theme/30-tables.css), so a floor written while the pane
|
||||
* was open pins the collapse open once it closes: on Network -> Interfaces the `interface`
|
||||
* pane held its 1265px after the reader left it, and the tab they were looking at started
|
||||
* that far down the page (issue #41). The clear-and-remeasure shape this replaced in 0.14.4
|
||||
* cleared every floor each tick, so a pane going inactive lost its own on the next one; only
|
||||
* a box that cannot hold anything up is cleared here, and it is re-measured the tick after it
|
||||
* comes back. A collapsed pane whose floor is still standing also measures a height of its
|
||||
* own, which is why refusing to write is not enough. */
|
||||
if (gone[i]) { dropFloor(box); return; }
|
||||
/* ZERO IS AN EMPTY BOX, and the floor it already wears is what holds it up — the moment this
|
||||
* whole mechanism exists for, since `dom.content()` empties before it refills.
|
||||
*
|
||||
* But only for the one pass. A box that is still empty on the next one is not refilling, and
|
||||
* its floor is then blank page that nothing takes back: measured on Network -> Interfaces,
|
||||
* a section emptied and left alone keeps 1299px of `min-height` for the life of the page,
|
||||
* with the document standing at 1720px around no content at all. That is what 0.14.4 changed
|
||||
* by replacing clear-and-remeasure, which read the collapsed height and wrote nothing. */
|
||||
if (hs[i] <= 0) {
|
||||
if (!box.style.minHeight) return;
|
||||
if (emptied.has(box)) { dropFloor(box); emptied.delete(box); }
|
||||
else { emptied.add(box); checkEmptyLater(); }
|
||||
return;
|
||||
}
|
||||
emptied.delete(box);
|
||||
const px = hs[i] + 'px';
|
||||
if (box.style.minHeight !== px) box.style.minHeight = px;
|
||||
if (box.style.minHeight !== px) {
|
||||
box.style.minHeight = px;
|
||||
box.setAttribute('data-fs-floor', '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -932,6 +1003,21 @@ return baseclass.extend({
|
||||
const room = this.roomFor(el);
|
||||
const grown = el.getBoundingClientRect().width;
|
||||
return Math.max(el.scrollWidth, grown) > room + 1; /* +1: sub-pixel rounding */
|
||||
},
|
||||
|
||||
/* IS SOMEBODY ELSE ALREADY SCROLLING THIS? An app that puts its table in a box of its own with
|
||||
* `overflow-x: auto` has answered the overflow question itself, and the theme re-laying that
|
||||
* table overrules a decision that was not its to take: luci-app-filemanager parks its listing in
|
||||
* a 598px `div.resizeable` and the whole table came out as cards on a 1280px screen, where the
|
||||
* page had 1224px of room and the reader had asked for none of it.
|
||||
*
|
||||
* The walk stops at the content root, so the theme's own scrollers are not this test's business:
|
||||
* `#modal_overlay` is the dialog's scroller (base/60-modal.css) and the scroll fallback the theme
|
||||
* gives a foreign table is on the TABLE itself (theme/30-tables.css), not on an ancestor. */
|
||||
inScroller(el) {
|
||||
for (let p = el.parentElement; p && p.id !== 'view' && p.id !== 'modal_overlay'; p = p.parentElement)
|
||||
if ((/(auto|scroll)/).test(window.getComputedStyle(p).overflowX)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -492,6 +492,21 @@ function fitTables() {
|
||||
return;
|
||||
}
|
||||
|
||||
/* SOMEBODY ELSE'S SCROLLER, SOMEBODY ELSE'S DECISION. An app that puts its table in a box of
|
||||
* its own with `overflow-x: auto` has already answered the question this file asks, and
|
||||
* every answer here would overrule it: inside a 598px box a wide table overflows by
|
||||
* definition, so the ladder ends in a card stack however much room the page has.
|
||||
* luci-app-filemanager parks its listing in a `div.resizeable` of exactly that width and
|
||||
* came out as cards on a 1280px screen, with 1224px of column beside it (docs/third-party-
|
||||
* apps.md: what the theme may do to a foreign table). Asked before the slot's answer is
|
||||
* applied, so a table that was carded before this rule existed is un-carded rather than
|
||||
* re-carded. */
|
||||
if (fit.inScroller(t)) {
|
||||
t.classList.add('fs-fitted');
|
||||
t.classList.remove('fs-stacked', 'fs-drop-xs');
|
||||
return;
|
||||
}
|
||||
|
||||
/* the column count, read from the tree: asking forces no layout */
|
||||
const shape = shapeOf(t);
|
||||
|
||||
|
||||
@@ -187,8 +187,31 @@
|
||||
input.focus();
|
||||
|
||||
/* if the router also serves HTTPS, hop over to it before the password goes over this
|
||||
* connection; the <img> probe is upstream's trick for proving the TLS port answers first */
|
||||
if (document.location.protocol === 'https:') return;
|
||||
* connection; the <img> probe is upstream's trick for proving the TLS port answers first
|
||||
*
|
||||
* THE HOP CAN ARRIVE WITHOUT THE SESSION. On Chromium the jump from http to https is
|
||||
* cross-site — schemeful same-site puts the two schemes in different sites — so the
|
||||
* browser withholds LuCI's `SameSite=strict` cookie (dispatcher.uc) on exactly that
|
||||
* navigation and a reader already logged in over https lands back on this form: 403 to
|
||||
* the hop against 200 to curl carrying the cookie, measured on a stand serving both
|
||||
* schemes. Firefox sends it and answers 200 straight away.
|
||||
*
|
||||
* So where this form is what the hop landed on, it is asked again once, over the same
|
||||
* scheme: that navigation is same-site everywhere and carries the cookie. The retry is
|
||||
* bounded by the REFERRER rather than by a marker in the URL — after it, the referrer is
|
||||
* this https page instead of the http one, so a reader who genuinely has no session sees
|
||||
* this form once and stays on it. A `#…` marker was tried and is not what ships: on
|
||||
* Firefox the retry never runs, so nothing takes the fragment back off the address. */
|
||||
if (document.location.protocol === 'https:') {
|
||||
/* the host is compared as a PARSED origin, not as a prefix of the string:
|
||||
* `http://192.168.1.1.example.com` starts with `http://192.168.1.1` */
|
||||
const from = document.referrer ? new URL(document.referrer) : null;
|
||||
|
||||
if (from && from.protocol === 'http:' && from.hostname === window.location.hostname)
|
||||
window.location.replace(window.location.href);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { hostname, pathname } = window.location;
|
||||
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@ MISE_HASH_x86_64:=3832f39c325e343f81fe3d92b2447c5d1a5eea1bc85092bb7b6c2580622264
|
||||
MISE_HASH_aarch64:=06186cfbfe947049b21d58575fb0ea800cc26ed1375f20f4b678cb3a9d679437
|
||||
|
||||
PKG_NAME:=mise
|
||||
PKG_VERSION:=2026.8.16
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=2026.9.0
|
||||
PKG_RELEASE:=4
|
||||
PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION)-linux-$(MISE_ARCH)-musl.tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/jdx/mise/releases/download/v$(PKG_VERSION)/
|
||||
PKG_HASH:=skip
|
||||
|
||||
+2
-2
@@ -7,9 +7,9 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=openlist2
|
||||
PKG_VERSION:=4.2.5
|
||||
PKG_VERSION:=4.2.6
|
||||
PKG_WEB_VERSION:=4.2.5
|
||||
PKG_RELEASE:=13
|
||||
PKG_RELEASE:=14
|
||||
|
||||
PKG_SOURCE:=openlist-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/OpenListTeam/OpenList/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=fastfetch
|
||||
PKG_VERSION:=2.67.1
|
||||
PKG_RELEASE:=14
|
||||
PKG_VERSION:=2.68.1
|
||||
PKG_RELEASE:=15
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/fastfetch-cli/fastfetch/tar.gz/$(PKG_VERSION)?
|
||||
|
||||
+2
-2
@@ -43,11 +43,11 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand
|
||||
PKG_RELEASE:=16
|
||||
PKG_RELEASE:=17
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||
PKG_SOURCE_VERSION:=68a3f844a310fe9d7216d8febc496856ee5e0054
|
||||
PKG_SOURCE_VERSION:=3e226ad39268a18b6f5a9a699b766aa601b2d904
|
||||
PKG_SOURCE_DATE:=2026-08-31
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
|
||||
Reference in New Issue
Block a user