🐶 Sync 2026-09-02 00:00:06

This commit is contained in:
github-actions[bot] committed 2026-09-02 00:00:06 +08:00
1 parent aee1f777fe
commit fe8c6d347a
38 files changed
+667 -102

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
+41 -4
View File
@@ -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,
+3
View File
@@ -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/'
+15
View File
@@ -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
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
View File
@@ -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
+41 -4
View File
@@ -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,
+4
View File
@@ -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'
+26 -2
View File
@@ -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
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
+2 -2
View File
@@ -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
View File
@@ -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)
+3 -3
View File
@@ -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)
+1
View File
@@ -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
+1 -1
View File
@@ -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
@@ -8,6 +8,18 @@
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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+176 -6
View File
@@ -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")%>')\"/>";
}
}
});
+1 -1
View File
@@ -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:=
@@ -1,3 +1,3 @@
var go=Object.defineProperty,vo=Object.defineProperties;var bo=Object.getOwnPropertyDescriptors;var xa=Object.getOwnPropertySymbols;var ho=Object.prototype.hasOwnProperty,_o=Object.prototype.propertyIsEnumerable;var We=(o,n,a)=>n in o?go(o,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):o[n]=a,st=(o,n)=>{for(var a in n||(n={}))ho.call(n,a)&&We(o,a,n[a]);if(xa)for(var a of xa(n))_o.call(n,a)&&We(o,a,n[a]);return o},vt=(o,n)=>vo(o,bo(n));var xo=(o,n)=>()=>(n||o((n={exports:{}}).exports,n),n.exports);var ve=(o,n,a)=>(We(o,typeof n!="symbol"?n+"":n,a),a);var L=(o,n,a)=>new Promise((l,u)=>{var d=g=>{try{k(a.next(g))}catch(p){u(p)}},c=g=>{try{k(a.throw(g))}catch(p){u(p)}},k=g=>g.done?l(g.value):Promise.resolve(g.value).then(d,c);k((a=a.apply(o,n)).next())});import{a as wo,c as ko,d as we,b as I,e as Q,u as e,o as r,f as s,g as t,t as i,n as ht,h as D,i as Y,r as _t,j as Yt,w as O,v as Rt,k as Ia,l as Z,m as V,p as E,q as $t,s as Pt,x as pt,F as U,y as tt,z as at,A as jt,B as ut,C as ct,D as ke,E as ee,T as St,G as Nt,H as lt,I as ft,J as xt,K as dt,L as gt,M as Bt,N as Xe,O as Ma,P as La,Q as Oa,R as ta,S as ea,U as Na,V as aa,W as Pe,X as yo,Y as oa,Z as ye,_ as Fo,$ as Eo,a0 as na,a1 as $o,a2 as Te,a3 as Co,a4 as Ie,a5 as Do,a6 as Bo,a7 as Yo,a8 as Ao,a9 as So,aa as zo,ab as Po,ac as To}from"./vendor.js?v=91daea62";var sT=xo(ae=>{const Io=function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))l(u);new MutationObserver(u=>{for(const d of u)if(d.type==="childList")for(const c of d.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&l(c)}).observe(document,{childList:!0,subtree:!0});function a(u){const d={};return u.integrity&&(d.integrity=u.integrity),u.referrerpolicy&&(d.referrerPolicy=u.referrerpolicy),u.crossorigin==="use-credentials"?d.credentials="include":u.crossorigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function l(u){if(u.ep)return;u.ep=!0;const d=a(u);fetch(u.href,d)}};Io();const Gt={language:void 0,numberFormat:new Intl.NumberFormat("en",{notation:"compact"})},Mo=o=>L(ae,null,function*(){const n=window.vue_lang;new Date().getTime();let a={};try{const d=yield wo({url:window.vue_lang_data,method:"GET"});d.data&&(a=d.data)}catch(d){console.log(d)}const l=ko({defaultLanguage:n,mutedLanguages:["zh-cn"],translations:a,setGlobalProperties:!1,provideDirective:!1,provideComponent:!1});o.use(l),Gt.language=l;const{$gettext:u}=l;window.$i18n=u;try{Gt.numberFormat=new Intl.NumberFormat(n,{notation:"compact"})}catch(d){console.error("Intl.NumberFormat unsupported lang",n,d)}}),J=()=>{if(Gt.language)return Gt.language;throw new Error("I18N Uninitialized!")},Me=()=>({$gettext:(o,n,a)=>{if(Gt.language)return Gt.language.$gettext(o,n,a);throw new Error("I18N Uninitialized!")},$ngettext:(o,n,a,l,u)=>{if(Gt.language)return Gt.language.$ngettext(o,n,a,l,u);throw new Error("I18N Uninitialized!")}}),De=o=>typeof o=="number"?Gt.numberFormat.format(o):"?",{$gettext:Lo,$ngettext:uT}=Me(),Va=(o,n)=>L(ae,null,function*(){return new Promise((a,l)=>L(ae,null,function*(){try{const u=yield fetch(o,n);if(Math.floor(u.status/100)!=2)throw u.status+" "+u.statusText;const d=st({},u);d.data=yield u.json(),a(d)}catch(u){const d=u;l(Lo("\u7F51\u7EDC\u5F02\u5E38\uFF1A")+((d==null?void 0:d.message)||u))}}))});class ia{constructor(n){ve(this,"config",{baseURL:"",headers:{}});ve(this,"useRequest",n=>n);ve(this,"useResponse",n=>n);ve(this,"useError",n=>n);n.baseURL&&(this.config.baseURL=n.baseURL),n.headers&&(this.config.headers=n.headers)}static create(n){return new ia(n)}Do(n,a){return L(this,null,function*(){return new Promise((l,u)=>L(this,null,function*(){try{const d=this.useRequest({baseURL:this.config.baseURL,headers:this.config.headers});n=`${d.baseURL||""}${n}`,a.headers==null&&(a.headers={}),d.headers&&(a.headers=st({},d.headers));const k=yield fetch(n,a),g=st({},k);g.data=yield k.json(),l(this.useResponse(g))}catch(d){this.useError(d),u(d)}}))})}TEXT(n,a){return L(this,null,function*(){return new Promise((l,u)=>L(this,null,function*(){try{const d=this.useRequest({baseURL:this.config.baseURL,headers:this.config.headers});n=`${d.baseURL||""}${n}`,a.headers==null&&(a.headers={}),d.headers&&(a.headers=st({},d.headers));const k=yield fetch(n,a),g=st({},k);g.data=yield k.text(),l(g)}catch(d){this.useError(d),u(d)}}))})}interceptors(){const n=this;return{requset:{use(a){n.useRequest=a}},response:{use(a,l){n.useResponse=a,l&&(n.useError=l)}}}}}const Ga=ia.create({});Ga.interceptors().requset.use(o=>o);Ga.interceptors().response.use(o=>(o.data&&o.data.success==null&&o.data.success==0,o));const{$gettext:ja,$ngettext:cT}=Me(),Oo="/cgi-bin/luci/istore",No="/cgi-bin/luci/admin";let Ae=!1;const W=(o,n)=>(o.indexOf("//")==-1&&(o=`${Oo}${o}`),Va(o,n).then(a=>(a!=null&&a.data&&a.data.success==-1001&&a.data.error=="Forbidden"&&(Ae||(Ae=!0,alert(ja("\u767B\u5F55\u8FC7\u671F\uFF0C\u8BF7\u91CLine truncated
var go=Object.defineProperty,vo=Object.defineProperties;var bo=Object.getOwnPropertyDescriptors;var xa=Object.getOwnPropertySymbols;var ho=Object.prototype.hasOwnProperty,_o=Object.prototype.propertyIsEnumerable;var We=(o,n,a)=>n in o?go(o,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):o[n]=a,st=(o,n)=>{for(var a in n||(n={}))ho.call(n,a)&&We(o,a,n[a]);if(xa)for(var a of xa(n))_o.call(n,a)&&We(o,a,n[a]);return o},vt=(o,n)=>vo(o,bo(n));var xo=(o,n)=>()=>(n||o((n={exports:{}}).exports,n),n.exports);var ve=(o,n,a)=>(We(o,typeof n!="symbol"?n+"":n,a),a);var L=(o,n,a)=>new Promise((l,u)=>{var d=g=>{try{k(a.next(g))}catch(p){u(p)}},c=g=>{try{k(a.throw(g))}catch(p){u(p)}},k=g=>g.done?l(g.value):Promise.resolve(g.value).then(d,c);k((a=a.apply(o,n)).next())});import{a as wo,c as ko,d as we,b as I,e as Q,u as e,o as r,f as s,g as t,t as i,n as ht,h as D,i as Y,r as _t,j as Yt,w as O,v as Rt,k as Ia,l as Z,m as V,p as E,q as $t,s as Pt,x as pt,F as U,y as tt,z as at,A as jt,B as ut,C as ct,D as ke,E as ee,T as St,G as Nt,H as lt,I as ft,J as xt,K as dt,L as gt,M as Bt,N as Xe,O as Ma,P as La,Q as Oa,R as ta,S as ea,U as Na,V as aa,W as Pe,X as yo,Y as oa,Z as ye,_ as Fo,$ as Eo,a0 as na,a1 as $o,a2 as Te,a3 as Co,a4 as Ie,a5 as Do,a6 as Bo,a7 as Yo,a8 as Ao,a9 as So,aa as zo,ab as Po,ac as To}from"./vendor.js?v=91daea62";var sT=xo(ae=>{const Io=function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))l(u);new MutationObserver(u=>{for(const d of u)if(d.type==="childList")for(const c of d.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&l(c)}).observe(document,{childList:!0,subtree:!0});function a(u){const d={};return u.integrity&&(d.integrity=u.integrity),u.referrerpolicy&&(d.referrerPolicy=u.referrerpolicy),u.crossorigin==="use-credentials"?d.credentials="include":u.crossorigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function l(u){if(u.ep)return;u.ep=!0;const d=a(u);fetch(u.href,d)}};Io();const Gt={language:void 0,numberFormat:new Intl.NumberFormat("en",{notation:"compact"})},Mo=o=>L(ae,null,function*(){const n=window.vue_lang;new Date().getTime();let a={};try{const d=yield wo({url:window.vue_lang_data,method:"GET"});d.data&&(a=d.data)}catch(d){console.log(d)}const l=ko({defaultLanguage:n,mutedLanguages:["zh-cn"],translations:a,setGlobalProperties:!1,provideDirective:!1,provideComponent:!1});o.use(l),Gt.language=l;const{$gettext:u}=l;window.$i18n=u;try{Gt.numberFormat=new Intl.NumberFormat(n,{notation:"compact"})}catch(d){console.error("Intl.NumberFormat unsupported lang",n,d)}}),J=()=>{if(Gt.language)return Gt.language;throw new Error("I18N Uninitialized!")},Me=()=>({$gettext:(o,n,a)=>{if(Gt.language)return Gt.language.$gettext(o,n,a);throw new Error("I18N Uninitialized!")},$ngettext:(o,n,a,l,u)=>{if(Gt.language)return Gt.language.$ngettext(o,n,a,l,u);throw new Error("I18N Uninitialized!")}}),De=o=>typeof o=="number"?Gt.numberFormat.format(o):"?",{$gettext:Lo,$ngettext:uT}=Me(),Va=(o,n)=>L(ae,null,function*(){return new Promise((a,l)=>L(ae,null,function*(){try{const u=yield fetch(o,n);if(Math.floor(u.status/100)!=2)throw u.status+" "+u.statusText;const d=st({},u);d.data=yield u.json(),a(d)}catch(u){const d=u;l(Lo("\u7F51\u7EDC\u5F02\u5E38\uFF1A")+((d==null?void 0:d.message)||u))}}))});class ia{constructor(n){ve(this,"config",{baseURL:"",headers:{}});ve(this,"useRequest",n=>n);ve(this,"useResponse",n=>n);ve(this,"useError",n=>n);n.baseURL&&(this.config.baseURL=n.baseURL),n.headers&&(this.config.headers=n.headers)}static create(n){return new ia(n)}Do(n,a){return L(this,null,function*(){return new Promise((l,u)=>L(this,null,function*(){try{const d=this.useRequest({baseURL:this.config.baseURL,headers:this.config.headers});n=`${d.baseURL||""}${n}`,a.headers==null&&(a.headers={}),d.headers&&(a.headers=st({},d.headers));const k=yield fetch(n,a),g=st({},k);g.data=yield k.json(),l(this.useResponse(g))}catch(d){this.useError(d),u(d)}}))})}TEXT(n,a){return L(this,null,function*(){return new Promise((l,u)=>L(this,null,function*(){try{const d=this.useRequest({baseURL:this.config.baseURL,headers:this.config.headers});n=`${d.baseURL||""}${n}`,a.headers==null&&(a.headers={}),d.headers&&(a.headers=st({},d.headers));const k=yield fetch(n,a),g=st({},k);g.data=yield k.text(),l(g)}catch(d){this.useError(d),u(d)}}))})}interceptors(){const n=this;return{requset:{use(a){n.useRequest=a}},response:{use(a,l){n.useResponse=a,l&&(n.useError=l)}}}}}const Ga=ia.create({});Ga.interceptors().requset.use(o=>o);Ga.interceptors().response.use(o=>(o.data&&o.data.success==null&&o.data.success==0,o));const{$gettext:ja,$ngettext:cT}=Me(),Oo="/cgi-bin/luci/istore",No="/cgi-bin/luci/admin";let Ae=!1;const W=(o,n)=>(o.indexOf("//")==-1&&(o=`${Oo}${o}`),Va(o,n).then(a=>(a!=null&&a.data&&a.data.success==-1001&&a.data.error=="Forbidden"&&(Ae||(Ae=!0,alert(ja("\u767B\u5F55\u8FC7\u671F\uFF0C\u8BF7\u91CLine truncated
`+k.value+`
`+d.value,disabled:""},null,8,RF)]),t("div",WF,[t("div",{class:"close",onClick:m,disabled:u.value},i(e(a)("\u5173\u95ED")),9,HF),u.value?D("",!0):(r(),s("div",{key:0,class:"next",onClick:v,disabled:u.value},i(e(a)("\u8FD0\u884C")),9,JF))])])]),_:1}))}});var KF=N(ZF,[["__scopeId","data-v-70c3aae0"]]);const QF={class:"actioner-container"},XF={class:"actioner-container_header"},tE={class:"tabs"},eE={class:"actioner-container_body"},aE={key:0,class:"table"},oE={class:"tr"},nE={class:"td left"},iE={class:"td left"},rE={class:"tr"},sE={class:"td left"},dE={class:"td left"},lE={class:"tr"},uE={class:"td left"},cE={class:"td left"},pE=["value"],fE=["value"],mE=["value"],gE={class:"actioner-container_footer"},vE=["disabled"],bE=I({props:{close:{type:Function,required:!0},disk:{type:Object,required:!0}},setup(o){const n=o,{$gettext:a,$ngettext:l}=J(),u=E(!1),d=E("info"),c=b=>{switch(d.value=b,b){case"info":break;case"attribute":m();break;case"log":p();break;case"extend":v();break}},k=()=>{u.value=!0,n.close()},g=gt({log:"",attribute:"",extend:""}),p=()=>L(this,null,function*(){try{const b=yield G.Smart.Test.Result.POST({type:"selftest",devicePath:n.disk.path||""});if(b.data){const{result:y,error:f}=b.data;y&&y.result&&(g.log=y.result),f&&(g.log=f)}}catch(b){g.log=b}}),m=()=>L(this,null,function*(){try{const b=yield G.Smart.Attribute.Result.POST({devicePath:n.disk.path||""});if(b.data){const{result:y,error:f}=b.data;y&&y.result&&(g.attribute=y.result),f&&(g.attribute=f)}}catch(b){g.attribute=b}}),v=()=>L(this,null,function*(){try{const b=yield G.Smart.Extend.Result.POST({devicePath:n.disk.path||""});if(b.data){const{result:y,error:f}=b.data;y&&y.result&&(g.extend=y.result),f&&(g.extend=f)}}catch(b){g.extend=b}});return(b,y)=>(r(),Z(je,null,{default:V(()=>[t("div",QF,[t("div",XF,[t("ul",tE,[t("li",{class:lt({"active cbi-tab":d.value=="info"}),onClick:y[0]||(y[0]=f=>c("info"))},[t("a",null,i(e(a)("\u8BBE\u5907\u4FE1\u606F")),1)],2),t("li",{class:lt({"active cbi-tab":d.value=="attribute"}),onClick:y[1]||(y[1]=f=>c("attribute"))},[t("a",null,i(e(a)("\u5C5E\u6027")),1)],2),t("li",{class:lt({"active cbi-tab":d.value=="log"}),onClick:y[2]||(y[2]=f=>c("log"))},[t("a",null,i(e(a)("\u81EA\u68C0\u65E5\u5FD7")),1)],2),t("li",{class:lt({"active cbi-tab":d.value=="extend"}),onClick:y[3]||(y[3]=f=>c("extend"))},[t("a",null,i(e(a)("\u6269\u5C55\u4FE1\u606F")),1)],2)])]),t("div",eE,[d.value=="info"?(r(),s("table",aE,[t("tr",oE,[t("td",nE,i(e(a)("\u8BBE\u5907")),1),t("td",iE,i(o.disk.path),1)]),t("tr",rE,[t("td",sE,i(e(a)("\u578B\u53F7")),1),t("td",dE,i(o.disk.model),1)]),t("tr",lE,[t("td",uE,i(e(a)("\u5E8F\u53F7")),1),t("td",cE,i(o.disk.serial),1)])])):d.value=="attribute"?(r(),s("textarea",{key:1,disabled:"",value:e(g).attribute},null,8,pE)):d.value=="log"?(r(),s("textarea",{key:2,disabled:"",value:e(g).log},null,8,fE)):d.value=="extend"?(r(),s("textarea",{key:3,disabled:"",value:e(g).extend},null,8,mE)):D("",!0)]),t("div",gE,[t("div",{class:"close",onClick:k,disabled:u.value},i(e(a)("\u5173\u95ED")),9,vE)])])]),_:1}))}});var hE=N(bE,[["__scopeId","data-v-313197ee"]]);const _E=o=>{const n=document.createElement("div");document.body.appendChild(n);const a=Y(tF,vt(st({},o),{close:()=>{l()}})),l=()=>{n.remove()};Ie(a,n)},xE=o=>{const n=document.createElement("div");document.body.appendChild(n);const a=Y(GF,vt(st({},o),{close:()=>{l()}})),l=()=>{n.remove()};Ie(a,n)},wE=o=>{const n=document.createElement("div");document.body.appendChild(n);const a=Y(KF,vt(st({},o),{close:()=>{l()}})),l=()=>{n.remove()};Ie(a,n)},kE=o=>{const n=document.createElement("div");document.body.appendChild(n);const a=Y(hE,vt(st({},o),{close:()=>{l()}})),l=()=>{n.remove()};Ie(a,n)},yE={class:"cbi-section"},FE={class:"cbi-value"},EE={class:"cbi-value-title"},$E={class:"cbi-value-field"},CE={class:"cbi-checkbox"},DE=["value"],BE={class:"cbi-value"},YE={class:"cbi-value-title"},AE={class:"cbi-value-field"},SE={class:"cbi-checkbox"},zE={value:"never"},PE={value:"sleep"},TE={value:"standby"},IE={value:"idle"},ME={class:"cbi-value-description"},LE=t("br",null,null,-1),OE=t("br",null,null,-1),NE=t("br",null,null,-1),VE=t("br",null,null,-1),GE={class:"cbi-value"},jE={class:"cbi-value-title"},UE={class:"cbi-value-field"},qE={class:"cbi-checkbox"},RE={value:0},WE=["value"],HE={class:"cbi-value-description"},JE={class:"cbi-value"},ZE={class:"cbi-value-title"},KE={class:"cbi-value-field"},QE={class:"cbi-checkbox"},XE={value:0},t$=["value"],e$={class:"cbi-value-description"},a$={class:"cbi-section cbi-tblsection",id:"cbi-nfs-mount"},o$={class:"table cbi-section-table"},n$={class:"tr cbi-section-table-titles anonymous"},i$={class:"th cbi-section-table-cell","data-widget":"value"},r$={class:"th cbi-section-table-cell","data-widget":"value"},s$={class:"th cbi-section-table-cell","data-widget":"value"},d$={class:"th cbi-section-table-cell","data-widget":"value"},l$={class:"th cbi-section-table-cell","data-widget":"value"},u$={class:"th cbi-section-table-cell","data-widget":"value" Line truncated
@@ -1,2 +1,2 @@
@keyframes bganimation-0698ba82{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter-0698ba82{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns-0698ba82{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active[data-v-0698ba82]{animation:rotateEnter-0698ba82 .7s;position:relative}.rotate-leave-active[data-v-0698ba82]{opacity:0;display:none;position:relative;z-index:-999}.app-container[data-v-0698ba82]{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}.disk-item-tooltip[data-v-0698ba82]{position:fixed;background:rgba(0,0,0,.7);z-index:10111;color:#fff;padding:.5rem 1rem;font-size:1em;min-width:200px;line-height:24px}.disk-item-tooltip[data-v-0698ba82]:after{content:"";position:absolute;bottom:-6px;border-color:#4c4c4c rgba(0,0,0,0) rgba(0,0,0,0);left:0;right:0;text-align:center;width:0;margin:0 auto;border-width:6px 8px 0;border-style:solid}#main .app-btn,#actioner .app-btn{min-height:36px}@keyframes bganimation-9bc295c2{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter-9bc295c2{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns-9bc295c2{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active[data-v-9bc295c2]{animation:rotateEnter-9bc295c2 .7s;position:relative}.rotate-leave-active[data-v-9bc295c2]{opacity:0;display:none;position:relative;z-index:-999}.app-container[data-v-9bc295c2]{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}#main[data-v-9bc295c2]{width:100%}#main[data-v-9bc295c2],#main[data-v-9bc295c2] *{-webkit-box-sizing:border-box;-webkit-tap-highlight-color:transparent;box-sizing:border-box;word-wrap:break-word;outline:none}@keyframes bganimation{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active{animation:rotateEnter .7s;position:relative}.rotate-leave-active{opacity:0;display:none;position:relative;z-index:-999}.app-container{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}:root{--flow-bg-color: #fff;--flow-span-color: rgba(0, 0, 0, .6);--card-bg-color: #fff;--card-box-shadow: 0 0 10px 1px #bfbfbf24;--app-container_title-color: #1e1e1e;--app-container_status-label_block: black;--item-label_key-span-color: #333;--item-label_value-span-color: #333;--app-container_status-label_bg: #f3f3f3;--item_btn-border: 1px solid #553afb;--item_btn-color: #553afb;--tit-color: #1e1e1e;--popup-bg-color: #fff;--tag-bg-color: #f5f5f5;--gradient-bg-color:linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.8) 62%, rgba(255,255,255,1) 100%);--breadcrumbs-tit-color:#d6dbf8;--breadcrumbs-tit-color1:#f1f2f9;--border-color: #e5e5e5;--SystemUpdateBanner-bg-color: #eff6ff;--SystemUpdateBanner-border-color: #bedbff;--btn-border-color: #e5e5e5;--card-txt-color: #32325d;--SystemInfo-bg-color:#f9fafd}@media (prefers-color-scheme: light){:root{--flow-bg-color: #fff;--flow-span-color: rgba(0, 0, 0, .6);--card-bg-color: #fff;--card-box-shadow: 0 0 10px 1px #bfbfbf24;--app-container_title-color: #1e1e1e;--app-container_status-label_block: black;--item-label_key-span-color: #333;--item-label_value-span-color: #333;--app-container_status-label_bg: #f3f3f3;--item_btn-border: 1px solid #553afb;--item_btn-color: #553afb;--tit-color: #1e1e1e;--popup-bg-color: #fff;--tag-Line truncated
@keyframes bganimation-0698ba82{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter-0698ba82{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns-0698ba82{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active[data-v-0698ba82]{animation:rotateEnter-0698ba82 .7s;position:relative}.rotate-leave-active[data-v-0698ba82]{opacity:0;display:none;position:relative;z-index:-999}.app-container[data-v-0698ba82]{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}.disk-item-tooltip[data-v-0698ba82]{position:fixed;background:rgba(0,0,0,.7);z-index:10111;color:#fff;padding:.5rem 1rem;font-size:1em;min-width:200px;line-height:24px}.disk-item-tooltip[data-v-0698ba82]:after{content:"";position:absolute;bottom:-6px;border-color:#4c4c4c rgba(0,0,0,0) rgba(0,0,0,0);left:0;right:0;text-align:center;width:0;margin:0 auto;border-width:6px 8px 0;border-style:solid}#main .app-btn,#actioner .app-btn{min-height:36px}@keyframes bganimation-9bc295c2{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter-9bc295c2{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns-9bc295c2{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active[data-v-9bc295c2]{animation:rotateEnter-9bc295c2 .7s;position:relative}.rotate-leave-active[data-v-9bc295c2]{opacity:0;display:none;position:relative;z-index:-999}.app-container[data-v-9bc295c2]{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}#main[data-v-9bc295c2]{width:100%}#main[data-v-9bc295c2],#main[data-v-9bc295c2] *{-webkit-box-sizing:border-box;-webkit-tap-highlight-color:transparent;box-sizing:border-box;word-wrap:break-word;outline:none}@keyframes bganimation{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active{animation:rotateEnter .7s;position:relative}.rotate-leave-active{opacity:0;display:none;position:relative;z-index:-999}.app-container{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}:root{--flow-bg-color: #fff;--flow-span-color: rgba(0, 0, 0, .6);--card-bg-color: #fff;--card-box-shadow: 0 0 10px 1px #bfbfbf24;--app-container_title-color: #1e1e1e;--app-container_status-label_block: black;--item-label_key-span-color: #333;--item-label_value-span-color: #333;--app-container_status-label_bg: #f3f3f3;--item_btn-border: 1px solid #553afb;--item_btn-color: #553afb;--tit-color: #1e1e1e;--popup-bg-color: #fff;--tag-bg-color: #f5f5f5;--gradient-bg-color:linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.8) 62%, rgba(255,255,255,1) 100%);--breadcrumbs-tit-color:#d6dbf8;--breadcrumbs-tit-color1:#f1f2f9;--border-color: #e5e5e5;--SystemUpdateBanner-bg-color: #eff6ff;--SystemUpdateBanner-border-color: #bedbff;--btn-border-color: #e5e5e5;--card-txt-color: #32325d;--SystemInfo-bg-color:#f9fafd}@media (prefers-color-scheme: light){:root{--flow-bg-color: #fff;--flow-span-color: rgba(0, 0, 0, .6);--card-bg-color: #fff;--card-box-shadow: 0 0 10px 1px #bfbfbf24;--app-container_title-color: #1e1e1e;--app-container_status-label_block: black;--item-label_key-span-color: #333;--item-label_value-span-color: #333;--app-container_status-label_bg: #f3f3f3;--item_btn-border: 1px solid #553afb;--item_btn-color: #553afb;--tit-color: #1e1e1e;--popup-bg-color: #fff;--tag-Line truncated
.page-container>div>div>a:first-child,.info_content>.status_box{display:none !important}
@@ -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")
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)?
+2 -2
View File
@@ -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
View File
@@ -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