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

This commit is contained in:
github-actions[bot]
2026-09-02 00:00:06 +08:00
parent 22565d103c
commit e9d23393c1
38 changed files with 685 additions and 120 deletions
+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/'
+22 -7
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
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
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'
+33 -9
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
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
+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
@@ -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)
{
+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:=
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")
+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