🦄 Sync 2026-08-26 20:42:53
No files matched your search
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_BAIDUDRIVE:=$(ARCH)
|
||||
|
||||
PKG_NAME:=baidudrive
|
||||
PKG_VERSION:=kaiplus-runtime-v1.0.5
|
||||
PKG_RELEASE:=15
|
||||
PKG_VERSION:=dockermanager-runtime-v0.1.1
|
||||
PKG_RELEASE:=16
|
||||
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
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#
|
||||
# Copyright (C) 2026 LinkEase Contributors
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_ARCH_DOCKERMANAGER:=$(ARCH)
|
||||
|
||||
PKG_NAME:=dockermanager
|
||||
PKG_VERSION:=dockermanager-runtime-v0.1.1
|
||||
PKG_RELEASE:=1
|
||||
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
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_VERSION)
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=System
|
||||
TITLE:=Docker Manager
|
||||
URL:=https://github.com/linkease/linkease-docker-manager
|
||||
DEPENDS:=+docker +dockerd +ca-bundle +luci-lib-linkeaseauth
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Docker Manager provides a LinkEase Desktop web module and HTTP API for Docker management.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/dockermanager
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/prerm
|
||||
#!/bin/sh
|
||||
if [ "$${IPKG_INSTROOT}" = "" ]; then
|
||||
if [ "$$(readlink /usr/share/linkeasefull/desktop-apps.d/20-dockermanager-plugin.json 2>/dev/null)" = "/usr/share/dockermanager/dockermanager-plugin.json" ]; then
|
||||
rm -f /usr/share/linkeasefull/desktop-apps.d/20-dockermanager-plugin.json
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin $(1)/usr/share/dockermanager/www $(1)/usr/share/linkeasefull/desktop-apps.d $(1)/etc/init.d $(1)/etc/config
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/docker-manager.$(PKG_ARCH_DOCKERMANAGER) $(1)/usr/sbin/docker-manager
|
||||
$(CP) $(PKG_BUILD_DIR)/www/* $(1)/usr/share/dockermanager/www/
|
||||
$(INSTALL_DATA) ./files/logo.svg $(1)/usr/share/dockermanager/www/logo.svg
|
||||
$(INSTALL_DATA) ./files/dockermanager-plugin.json $(1)/usr/share/dockermanager/dockermanager-plugin.json
|
||||
ln -sf /usr/share/dockermanager/dockermanager-plugin.json $(1)/usr/share/linkeasefull/desktop-apps.d/20-dockermanager-plugin.json
|
||||
$(INSTALL_BIN) ./files/dockermanager.init $(1)/etc/init.d/dockermanager
|
||||
$(INSTALL_CONF) ./files/dockermanager.config $(1)/etc/config/dockermanager
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "dockermanager",
|
||||
"name": "Docker Manager",
|
||||
"icon": "logo.svg",
|
||||
"staticRoot": "/usr/share/dockermanager/www",
|
||||
"desktop": {
|
||||
"mode": "module",
|
||||
"entry": "desktop-entry.js",
|
||||
"isolation": "shadow-dom"
|
||||
},
|
||||
"standalone": {
|
||||
"entry": "index.html",
|
||||
"basePath": "/apps/dockermanager/"
|
||||
},
|
||||
"backend": {
|
||||
"type": "http",
|
||||
"scheme": "http",
|
||||
"host": "127.0.0.1",
|
||||
"portFromUci": "dockermanager.@dockermanager[0].port",
|
||||
"defaultPort": 8192,
|
||||
"upstreamBasePath": "/apps/dockermanager/",
|
||||
"apiPath": "api/",
|
||||
"pathMode": "preserve"
|
||||
},
|
||||
"window": {
|
||||
"width": 1180,
|
||||
"height": 760,
|
||||
"singleton": true
|
||||
},
|
||||
"capabilities": ["docker"],
|
||||
"categories": ["system"]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
config dockermanager
|
||||
option 'enabled' '0'
|
||||
option 'data_dir' ''
|
||||
option 'port' '8192'
|
||||
option 'bind_addr' '0.0.0.0'
|
||||
option 'base_path' '/apps/dockermanager/'
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=94
|
||||
USE_PROCD=1
|
||||
|
||||
get_config() {
|
||||
config_get_bool enabled "$1" enabled 0
|
||||
config_get data_dir "$1" data_dir ""
|
||||
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/"
|
||||
}
|
||||
|
||||
mkdir_p() {
|
||||
[ -d "$1" ] || mkdir -p "$1"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load dockermanager
|
||||
config_foreach get_config dockermanager
|
||||
|
||||
if [ "$enabled" != 1 ]; then
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$data_dir" ]; then
|
||||
logger -t dockermanager "data_dir is empty; set option data_dir in /etc/config/dockermanager"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir_p "$data_dir" || return 1
|
||||
mkdir_p "$data_dir/state" || return 1
|
||||
mkdir_p "$data_dir/cache" || return 1
|
||||
|
||||
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"
|
||||
procd_set_param command /usr/sbin/docker-manager
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
logger -t dockermanager "Docker Manager service started"
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "dockermanager"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" role="img" aria-label="Docker Manager">
|
||||
<rect width="96" height="96" rx="18" fill="#111827"/>
|
||||
<path fill="#60a5fa" d="M20 53h56c-1 13-11 23-26 23H38c-11 0-18-7-18-18v-5Z"/>
|
||||
<rect x="25" y="36" width="10" height="10" rx="2" fill="#dbeafe"/>
|
||||
<rect x="38" y="36" width="10" height="10" rx="2" fill="#dbeafe"/>
|
||||
<rect x="51" y="36" width="10" height="10" rx="2" fill="#dbeafe"/>
|
||||
<rect x="38" y="23" width="10" height="10" rx="2" fill="#dbeafe"/>
|
||||
<rect x="51" y="23" width="10" height="10" rx="2" fill="#dbeafe"/>
|
||||
<path fill="#93c5fd" d="M70 44c5-1 9-4 11-8 2 6-1 12-7 15l-4-7Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 658 B |
@@ -11,8 +11,8 @@ PKG_ARCH_ISTOREENHANCE:=$(ARCH)
|
||||
|
||||
PKG_NAME:=istoreenhance
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=0.7.15
|
||||
PKG_RELEASE:=5
|
||||
PKG_SOURCE_DATE:=0.7.16
|
||||
PKG_RELEASE:=6
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
ARCH_HEXCODE=8664
|
||||
@@ -29,6 +29,7 @@ PKG_SOURCE_URL:=https://github.com/kspeeder/docker_kspeeder/releases/download/v$
|
||||
PKG_HASH:=skip
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/iStoreEnhance-binary-$(PKG_SOURCE_DATE)
|
||||
KSPEEDER_WEB_ROOT:=$(PKG_BUILD_DIR)/wwwroot
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
@@ -61,6 +62,16 @@ if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
fi
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/prerm
|
||||
#!/bin/sh
|
||||
if [ "$${IPKG_INSTROOT}" = "" ]; then
|
||||
if [ "$$(readlink /usr/share/linkeasefull/desktop-apps.d/20-kspeeder.json 2>/dev/null)" = "/usr/share/kspeeder/kspeeder-plugin.json" ]; then
|
||||
rm -f /usr/share/linkeasefull/desktop-apps.d/20-kspeeder.json
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
@@ -68,11 +79,20 @@ define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d $(1)/etc/uci-defaults
|
||||
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d $(1)/etc/uci-defaults $(1)/usr/share/kspeeder/www $(1)/usr/share/linkeasefull/desktop-apps.d
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/iStoreEnhance.$(PKG_ARCH_ISTOREENHANCE) $(1)/usr/sbin/iStoreEnhance
|
||||
$(INSTALL_CONF) ./files/istoreenhance.config $(1)/etc/config/istoreenhance
|
||||
$(INSTALL_BIN) ./files/istoreenhance.init $(1)/etc/init.d/istoreenhance
|
||||
$(INSTALL_BIN) ./files/istoreenhance.uci-default $(1)/etc/uci-defaults/istoreenhance
|
||||
$(INSTALL_DATA) ./files/kspeeder-plugin.json $(1)/usr/share/kspeeder/kspeeder-plugin.json
|
||||
if [ -d "$(KSPEEDER_WEB_ROOT)" ] && [ -f "$(KSPEEDER_WEB_ROOT)/desktop-entry.js" ]; then \
|
||||
$(CP) $(KSPEEDER_WEB_ROOT)/* $(1)/usr/share/kspeeder/www/; \
|
||||
else \
|
||||
$(INSTALL_DATA) ./files/www/desktop-entry.js $(1)/usr/share/kspeeder/www/desktop-entry.js; \
|
||||
$(INSTALL_DATA) ./files/www/index.html $(1)/usr/share/kspeeder/www/index.html; \
|
||||
fi
|
||||
$(INSTALL_DATA) ./files/logo.png $(1)/usr/share/kspeeder/www/logo.png
|
||||
ln -sf /usr/share/kspeeder/kspeeder-plugin.json $(1)/usr/share/linkeasefull/desktop-apps.d/20-kspeeder.json
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "kspeeder",
|
||||
"name": "KSpeeder",
|
||||
"icon": "logo.png",
|
||||
"staticRoot": "/usr/share/kspeeder/www",
|
||||
"desktop": {
|
||||
"mode": "module",
|
||||
"entry": "desktop-entry.js",
|
||||
"isolation": "shadow-dom"
|
||||
},
|
||||
"standalone": {
|
||||
"entry": "index.html",
|
||||
"basePath": "/apps/kspeeder/",
|
||||
"externalOpen": {
|
||||
"enabled": true,
|
||||
"label": "打开 KSpeeder 独立页面",
|
||||
"scheme": "http",
|
||||
"hostMode": "request-host",
|
||||
"defaultPort": 5003,
|
||||
"path": "/"
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"type": "http",
|
||||
"scheme": "http",
|
||||
"host": "127.0.0.1",
|
||||
"portFromUci": "istoreenhance.@istoreenhance[0].adminport",
|
||||
"defaultPort": 5003,
|
||||
"upstreamBasePath": "/apps/kspeeder/",
|
||||
"apiPath": "api/",
|
||||
"pathMode": "preserve"
|
||||
},
|
||||
"window": {
|
||||
"width": 1040,
|
||||
"height": 700,
|
||||
"singleton": true
|
||||
},
|
||||
"capabilities": ["docker-acceleration", "registry-mirror", "cache"],
|
||||
"categories": ["network", "docker"]
|
||||
}
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,260 @@
|
||||
const stateByElement = new WeakMap();
|
||||
|
||||
function normalizeBasePath(path) {
|
||||
const value = typeof path === "string" && path.trim() ? path.trim() : "/apps/kspeeder/";
|
||||
return value.endsWith("/") ? value : `${value}/`;
|
||||
}
|
||||
|
||||
function normalizeAPIBase(context) {
|
||||
if (context && typeof context.apiBase === "string" && context.apiBase.trim()) {
|
||||
return context.apiBase.endsWith("/") ? context.apiBase : `${context.apiBase}/`;
|
||||
}
|
||||
return `${normalizeBasePath(context && context.basePath)}api/`;
|
||||
}
|
||||
|
||||
function escapeHTML(value) {
|
||||
return String(value == null ? "" : value).replace(/[&<>"']/g, (char) => ({
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
})[char]);
|
||||
}
|
||||
|
||||
async function fetchJSON(url, options) {
|
||||
const response = await fetch(url, {
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" },
|
||||
...options,
|
||||
});
|
||||
const text = await response.text();
|
||||
let payload = null;
|
||||
if (text) {
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch (error) {
|
||||
throw new Error(`API returned non-JSON response (${response.status})`);
|
||||
}
|
||||
}
|
||||
if (!response.ok) {
|
||||
const message = payload && (payload.message || payload.error || payload.details);
|
||||
throw new Error(message || `API request failed (${response.status})`);
|
||||
}
|
||||
return payload || {};
|
||||
}
|
||||
|
||||
function mirrorCounts(overview) {
|
||||
const mirrorsByProfile = overview && typeof overview.mirrors === "object" ? overview.mirrors : {};
|
||||
const result = { online: 0, total: 0, servedSpeed: "", servedDownload: "" };
|
||||
Object.values(mirrorsByProfile).forEach((profile) => {
|
||||
const mirrors = Array.isArray(profile && profile.mirrors) ? profile.mirrors : [];
|
||||
result.total += mirrors.length;
|
||||
result.online += mirrors.filter((mirror) => mirror && mirror.status === "online").length;
|
||||
if (!result.servedSpeed && profile && profile.served_speed) {
|
||||
result.servedSpeed = profile.served_speed;
|
||||
}
|
||||
if (!result.servedDownload && profile && profile.served_download) {
|
||||
result.servedDownload = profile.served_download;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function profileRows(overview) {
|
||||
const mirrorsByProfile = overview && typeof overview.mirrors === "object" ? overview.mirrors : {};
|
||||
return Object.entries(mirrorsByProfile).map(([profile, status]) => {
|
||||
const mirrors = Array.isArray(status && status.mirrors) ? status.mirrors : [];
|
||||
const online = mirrors.filter((mirror) => mirror && mirror.status === "online").length;
|
||||
const best = mirrors.find((mirror) => mirror && mirror.status === "online") || mirrors[0] || {};
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHTML(profile)}</td>
|
||||
<td>${online}/${mirrors.length}</td>
|
||||
<td>${escapeHTML(best.current_speed || best.test_speed || "-")}</td>
|
||||
<td>${escapeHTML(best.name || "-")}</td>
|
||||
<td>${escapeHTML(best.error_info || "-")}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function render(root, model) {
|
||||
const counts = mirrorCounts(model.overview);
|
||||
const source = model.overview && model.overview.source ? model.overview.source : "-";
|
||||
const error = model.error ? `<div class="notice danger">${escapeHTML(model.error)}</div>` : "";
|
||||
const refreshing = model.refreshing ? "disabled" : "";
|
||||
const rows = profileRows(model.overview);
|
||||
root.innerHTML = `
|
||||
<style>
|
||||
:host { color-scheme: light; }
|
||||
.ks-page { box-sizing: border-box; min-height: 100%; background: #f7f8fa; color: #1f2937; font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
||||
.ks-wrap { box-sizing: border-box; max-width: 1080px; margin: 0 auto; padding: 18px; }
|
||||
.ks-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 14px; }
|
||||
h1 { margin: 0; font-size: 20px; line-height: 1.2; font-weight: 650; color: #111827; }
|
||||
.sub { margin-top: 5px; color: #667085; font-size: 13px; }
|
||||
.actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
|
||||
button { border: 1px solid #cfd7e3; background: #fff; color: #1f2937; border-radius: 6px; padding: 7px 10px; font-size: 13px; cursor: pointer; }
|
||||
button.primary { background: #0f766e; border-color: #0f766e; color: #fff; }
|
||||
button:disabled { cursor: wait; opacity: .62; }
|
||||
.grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
|
||||
.card { border: 1px solid #e2e8f0; border-radius: 8px; background: #fff; padding: 12px; min-width: 0; }
|
||||
.label { color: #667085; font-size: 12px; margin-bottom: 6px; }
|
||||
.value { font-size: 18px; font-weight: 650; color: #111827; overflow-wrap: anywhere; }
|
||||
.notice { border-radius: 8px; padding: 10px 12px; margin-bottom: 12px; background: #fff7ed; border: 1px solid #fed7aa; color: #9a3412; }
|
||||
.notice.danger { background: #fef2f2; border-color: #fecaca; color: #b91c1c; }
|
||||
.section { margin-top: 12px; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; background: #fff; }
|
||||
.section-title { display: flex; justify-content: space-between; gap: 10px; padding: 11px 12px; border-bottom: 1px solid #e2e8f0; font-weight: 650; }
|
||||
table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||
th, td { padding: 9px 12px; border-bottom: 1px solid #edf2f7; text-align: left; vertical-align: top; overflow-wrap: anywhere; }
|
||||
th { color: #667085; font-size: 12px; font-weight: 600; background: #fbfcfd; }
|
||||
tr:last-child td { border-bottom: 0; }
|
||||
.empty { padding: 18px 12px; color: #667085; }
|
||||
@media (max-width: 720px) {
|
||||
.ks-wrap { padding: 12px; }
|
||||
.ks-top { display: block; }
|
||||
.actions { justify-content: flex-start; margin-top: 10px; }
|
||||
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
th:nth-child(5), td:nth-child(5) { display: none; }
|
||||
}
|
||||
@media (max-width: 430px) {
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
th:nth-child(4), td:nth-child(4) { display: none; }
|
||||
}
|
||||
</style>
|
||||
<div class="ks-page">
|
||||
<div class="ks-wrap">
|
||||
<div class="ks-top">
|
||||
<div>
|
||||
<h1>KSpeeder</h1>
|
||||
<div class="sub">Docker 镜像下载加速状态</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button data-action="reload">刷新</button>
|
||||
<button class="primary" data-action="refresh-mirrors" ${refreshing}>${model.refreshing ? "测速中" : "刷新测速"}</button>
|
||||
</div>
|
||||
</div>
|
||||
${error}
|
||||
<div class="grid">
|
||||
<div class="card"><div class="label">版本</div><div class="value">${escapeHTML(model.summary.version || "-")}</div></div>
|
||||
<div class="card"><div class="label">可用节点</div><div class="value">${counts.online}/${counts.total}</div></div>
|
||||
<div class="card"><div class="label">当前下发</div><div class="value">${escapeHTML(counts.servedSpeed || "-")}</div></div>
|
||||
<div class="card"><div class="label">累计下发</div><div class="value">${escapeHTML(counts.servedDownload || "-")}</div></div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title"><span>镜像节点</span><span>${escapeHTML(source)}</span></div>
|
||||
${rows ? `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>在线</th>
|
||||
<th>速度</th>
|
||||
<th>节点</th>
|
||||
<th>错误</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
` : '<div class="empty">暂无镜像状态,确认 iStoreEnhance 服务已启动并已配置缓存目录。</div>'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadModel(state) {
|
||||
const [summary, overview] = await Promise.all([
|
||||
fetchJSON(`${state.apiBase}summary`),
|
||||
fetchJSON(`${state.apiBase}mirrors/overview`),
|
||||
]);
|
||||
state.model.summary = summary;
|
||||
state.model.overview = overview;
|
||||
state.model.error = "";
|
||||
}
|
||||
|
||||
async function reload(state) {
|
||||
try {
|
||||
await loadModel(state);
|
||||
} catch (error) {
|
||||
state.model.error = error instanceof Error ? error.message : "加载失败";
|
||||
} finally {
|
||||
render(state.root, state.model);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshMirrors(state) {
|
||||
state.model.refreshing = true;
|
||||
state.model.error = "";
|
||||
render(state.root, state.model);
|
||||
try {
|
||||
await fetchJSON(`${state.apiBase}mirrors/refresh`, { method: "POST" });
|
||||
await loadModel(state);
|
||||
if (state.hostAdapter && state.hostAdapter.toast) {
|
||||
state.hostAdapter.toast.success("KSpeeder 测速已刷新");
|
||||
}
|
||||
} catch (error) {
|
||||
state.model.error = error instanceof Error ? error.message : "刷新测速失败";
|
||||
if (state.hostAdapter && state.hostAdapter.toast) {
|
||||
state.hostAdapter.toast.error(state.model.error);
|
||||
}
|
||||
} finally {
|
||||
state.model.refreshing = false;
|
||||
render(state.root, state.model);
|
||||
}
|
||||
}
|
||||
|
||||
export async function bootstrap() {}
|
||||
|
||||
export async function mount(props) {
|
||||
const root = props && props.domElement;
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
const context = props.context || {};
|
||||
const state = {
|
||||
root,
|
||||
apiBase: normalizeAPIBase(context),
|
||||
hostAdapter: props.hostAdapter,
|
||||
model: {
|
||||
summary: {},
|
||||
overview: {},
|
||||
error: "",
|
||||
refreshing: false,
|
||||
},
|
||||
};
|
||||
stateByElement.set(root, state);
|
||||
root.addEventListener("click", handleClick);
|
||||
render(root, state.model);
|
||||
await reload(state);
|
||||
}
|
||||
|
||||
export async function unmount(props) {
|
||||
const root = props && props.domElement;
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
root.removeEventListener("click", handleClick);
|
||||
root.innerHTML = "";
|
||||
stateByElement.delete(root);
|
||||
}
|
||||
|
||||
function handleClick(event) {
|
||||
const target = event.target && event.target.closest ? event.target.closest("[data-action]") : null;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const root = event.currentTarget;
|
||||
const state = stateByElement.get(root);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
const action = target.getAttribute("data-action");
|
||||
if (action === "reload") {
|
||||
void reload(state);
|
||||
} else if (action === "refresh-mirrors") {
|
||||
void refreshMirrors(state);
|
||||
}
|
||||
}
|
||||
|
||||
export default { bootstrap, mount, unmount };
|
||||
@@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>KSpeeder</title>
|
||||
<style>
|
||||
html, body, #root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">
|
||||
import { mount } from "./desktop-entry.js";
|
||||
|
||||
mount({
|
||||
domElement: document.getElementById("root"),
|
||||
context: {
|
||||
basePath: "/apps/kspeeder/",
|
||||
apiBase: "/apps/kspeeder/api/",
|
||||
app: { id: "kspeeder", name: "KSpeeder", entry: "desktop-entry.js" },
|
||||
window: { id: "standalone", title: "KSpeeder" },
|
||||
theme: { mode: "light", variant: "" }
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,8 +9,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_kaiplus:=$(ARCH)
|
||||
|
||||
PKG_NAME:=kaiplus
|
||||
PKG_VERSION:=kaiplus-runtime-v1.0.5
|
||||
PKG_RELEASE:=5
|
||||
PKG_VERSION:=dockermanager-runtime-v0.1.1
|
||||
PKG_RELEASE:=6
|
||||
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
|
||||
|
||||
@@ -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:=14
|
||||
PKG_RELEASE:=15
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc
|
||||
PKG_SOURCE_VERSION:=b0d9136d243ac1a5c5290190ffa6f66265aa7fba
|
||||
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)
|
||||
|
||||
@@ -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:=17
|
||||
PKG_RELEASE:=18
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc
|
||||
PKG_SOURCE_VERSION:=b0d9136d243ac1a5c5290190ffa6f66265aa7fba
|
||||
PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||
|
||||
@@ -10,8 +10,8 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
|
||||
PKG_NAME:=linkeasefull
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=3.0.12
|
||||
PKG_RELEASE:=16
|
||||
PKG_SOURCE_DATE:=3.0.13
|
||||
PKG_RELEASE:=17
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -24,7 +24,7 @@ LINKEASE_RUNTIME_ARCH:=arm64
|
||||
PKG_HASH:=skip
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc
|
||||
PKG_SOURCE_VERSION:=b0d9136d243ac1a5c5290190ffa6f66265aa7fba
|
||||
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)
|
||||
|
||||
@@ -52,10 +52,6 @@ is_persistent_data_root_parent() {
|
||||
return 0
|
||||
}
|
||||
|
||||
linkease_enabled() {
|
||||
[ "$(uci -q get linkease.@linkease[0].enabled 2>/dev/null)" = "1" ]
|
||||
}
|
||||
|
||||
read_config() {
|
||||
section="$1"
|
||||
config_get_bool enabled "$section" enabled 1
|
||||
@@ -80,10 +76,6 @@ start_service() {
|
||||
config_foreach read_config linkeasefull
|
||||
[ "$enabled" = "1" ] || return 1
|
||||
[ -x "$PROG" ] || return 1
|
||||
if linkease_enabled; then
|
||||
echo "linkeasefull: linkease is enabled; please disable linkease before starting linkeasefull" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$LOCAL_API"
|
||||
if [ -n "$data_root_parent" ]; then
|
||||
|
||||
@@ -12,7 +12,7 @@ LUCI_PKGARCH:=all
|
||||
|
||||
PKG_NAME:=luci-app-accesscontrol-plus
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=3
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -15,7 +15,8 @@ end
|
||||
|
||||
function act_status()
|
||||
local e = {}
|
||||
e.running = luci.sys.call("iptables -t filter -S MIAPLUS >/dev/null 2>&1") == 0
|
||||
-- 交给 init 脚本的 status 方法判断:它会按 fw3(iptables)/fw4(nft) 自动分发
|
||||
e.running = luci.sys.call("/etc/init.d/miaplus status >/dev/null 2>&1") == 0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
@@ -16,13 +16,42 @@ e = t:option(Flag, "enable", translate("Enabled"))
|
||||
e.rmempty = false
|
||||
e.default = "1"
|
||||
|
||||
-- HH:MM / H:MM -> 分钟;非法或空返回 nil
|
||||
local function time_to_min(s)
|
||||
local h, m = s:match("^(%d?%d):(%d%d)$")
|
||||
if not h then return nil end
|
||||
h, m = tonumber(h), tonumber(m)
|
||||
if h > 23 or m > 59 then return nil end
|
||||
return h * 60 + m
|
||||
end
|
||||
|
||||
e = t:option(Value, "timeon", translate("Start time"))
|
||||
e.optional = false
|
||||
e.default = "00:00"
|
||||
function e.validate(self, value, section)
|
||||
local mins = time_to_min(value)
|
||||
if not mins then
|
||||
return nil, translate("Invalid time, expected HH:MM")
|
||||
end
|
||||
return string.format("%02d:%02d", math.floor(mins / 60), mins % 60)
|
||||
end
|
||||
|
||||
e = t:option(Value, "timeoff", translate("End time"))
|
||||
e.optional=false
|
||||
e.default = "23:59"
|
||||
function e.validate(self, value, section)
|
||||
local mins = time_to_min(value)
|
||||
if not mins then
|
||||
return nil, translate("Invalid time, expected HH:MM")
|
||||
end
|
||||
-- 跨字段:起止时间不能相同(起止相同即无管控,易误配)
|
||||
local on = self.map:formvalue("cbid.miaplus." .. section .. ".timeon")
|
||||
local on_min = on and time_to_min(on)
|
||||
if on_min ~= nil and on_min == mins then
|
||||
return nil, translate("Start and end time must differ")
|
||||
end
|
||||
return string.format("%02d:%02d", math.floor(mins / 60), mins % 60)
|
||||
end
|
||||
|
||||
e = t:option(Flag, "z1", translate("Mon"))
|
||||
e.rmempty = true
|
||||
|
||||
@@ -10,8 +10,19 @@ START=30
|
||||
|
||||
CONFIG=miaplus
|
||||
|
||||
# 非 procd 脚本:status 命令不在 rc.common 默认命令表里,需显式注册
|
||||
EXTRA_COMMANDS="status"
|
||||
EXTRA_HELP=" status Show service status (auto fw3/fw4)"
|
||||
|
||||
. /lib/functions.sh
|
||||
|
||||
# 检测当前使用的防火墙框架:fw4 (nftables) 还是 fw3 (iptables)
|
||||
# 注意:fw4 与 fw3 共用 /etc/config/firewall,不存在 /etc/config/fw4,
|
||||
# 只能靠 fw4 / nft 命令是否存在来判定。
|
||||
have_fw4() {
|
||||
command -v fw4 >/dev/null 2>&1 && command -v nft >/dev/null 2>&1
|
||||
}
|
||||
|
||||
uci_export_section_name() {
|
||||
local ret=$(uci -n export $CONFIG | grep "config $1" | awk '{print $3}' | awk -F\' '{print $2}')
|
||||
echo ${ret:=$2}
|
||||
@@ -62,11 +73,23 @@ add_template_rule(){
|
||||
config_get enable "$section" enable 0
|
||||
[ "$enable" = "1" ] && add_rule "$section"
|
||||
}
|
||||
# 分发到具体实现
|
||||
add_rule(){
|
||||
if have_fw4; then
|
||||
add_rule_nft "$1"
|
||||
else
|
||||
add_rule_ipt "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# fw3 (iptables / ip6tables)
|
||||
# =====================================================================
|
||||
add_rule_ipt(){
|
||||
ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
|
||||
local macaddrs=$(uci_get_mac_by_template $1 '')
|
||||
if [ -z "$macaddrs" ]; then
|
||||
exit 0
|
||||
return 0
|
||||
fi
|
||||
for macaddr in $macaddrs
|
||||
do
|
||||
@@ -110,21 +133,9 @@ add_rule(){
|
||||
done
|
||||
}
|
||||
|
||||
del_rule(){
|
||||
type=$1
|
||||
blackMacAdd=$(iptables -t nat -L $type | grep -w RETURN | grep -w "MAC" | awk '{print $7}')
|
||||
[ -n "$blackMacAdd" ] && {
|
||||
for macaddrb in $blackMacAdd
|
||||
do
|
||||
iptables -t nat -D $type -m mac --mac-source $macaddrb -j RETURN
|
||||
done
|
||||
}
|
||||
}
|
||||
|
||||
start(){
|
||||
stop
|
||||
start_ipt(){
|
||||
enable=$(uci -q get miaplus.@basic[0].enable)
|
||||
[ "${enable:-0}" -eq 0 ] && exit 0
|
||||
[ "${enable:-0}" -eq 0 ] && return 0
|
||||
iptables -t filter -N MIAPLUS
|
||||
iptables -I INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
||||
iptables -I INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
||||
@@ -144,7 +155,7 @@ start(){
|
||||
fi
|
||||
add_rules
|
||||
}
|
||||
status(){
|
||||
status_ipt(){
|
||||
if iptables -t filter -S MIAPLUS >/dev/null 2>&1; then
|
||||
echo "running"
|
||||
return 0
|
||||
@@ -153,7 +164,7 @@ status(){
|
||||
echo "not running"
|
||||
return 1
|
||||
}
|
||||
stop(){
|
||||
stop_ipt(){
|
||||
iptables -t filter -D FORWARD -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
||||
iptables -D INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
||||
iptables -D INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
||||
@@ -173,3 +184,260 @@ stop(){
|
||||
ip6tables -t filter -X MIAPLUS 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# fw4 (nftables)
|
||||
# 使用独立表 inet miaplus,避免与 fw4 自身的 nftables 规则冲突。
|
||||
#
|
||||
# table inet miaplus {
|
||||
# chain input { type filter hook input priority 0; } # 仅匹配 53 端口
|
||||
# chain prerouting{ type nat hook prerouting priority dstnat; } # DNS 重定向到本机
|
||||
# }
|
||||
#
|
||||
# DNS 语义与 fw3 一致:只对发往 53 端口的流量做上网控制。
|
||||
#
|
||||
# NAT 竞争说明:本表 prerouting 链与 fw4 主表(inet fw4)的 PREROUTING/dstnat 同为
|
||||
# priority -100(dstnat)。nf_tables 允许多表同优先级共存,但同一数据包的同一 hook
|
||||
# 优先级内按表加载顺序处理,conntrack binding(redirect/redirect)先到先得。若 fw4
|
||||
# 自身对 DNS(53) 也做了重定向/端口转发,两者可能互抢,现象以先加载者为准。
|
||||
# 一般情况下 DNS 默认直通本机,二者不冲突;如遇规则不生效,优先排查此项。
|
||||
# =====================================================================
|
||||
|
||||
# 构造 nft 的星期集合片段,如 "{ 1, 2 }" / "5" / 空(不限制)
|
||||
# z1..z7 => 周一..周日;nft meta day: 0=Sun,1=Mon,...6=Sat => z1->1 ...
|
||||
nft_dayterm() {
|
||||
local days="" n=1 day
|
||||
for z in "$1" "$2" "$3" "$4" "$5" "$6" "$7"; do
|
||||
[ "$z" = "1" ] || { n=$((n+1)); continue; }
|
||||
day=$(( n % 7 )) # z1..z7 -> 1,2,3,4,5,6,0
|
||||
days="${days:+$days, }$day"
|
||||
n=$((n+1))
|
||||
done
|
||||
if [ -z "$days" ]; then
|
||||
echo ""
|
||||
elif echo "$days" | grep -q ,; then
|
||||
echo "{ $days }"
|
||||
else
|
||||
echo "$days"
|
||||
fi
|
||||
}
|
||||
|
||||
# 去掉前导零(防空串转成 "0"),规避 08/09 被当八进制的算术陷阱
|
||||
_digits() {
|
||||
echo "$1" | sed -e 's/^0*//' -e 's/^$/0/'
|
||||
}
|
||||
# HH:MM -> 分钟数(输入可为 H:M 或 HH:MM,非零填充)
|
||||
_mins() {
|
||||
local h m
|
||||
h=$(_digits "${1%%:*}")
|
||||
m=$(_digits "${1#*:}")
|
||||
echo $(( ${h:-0} * 60 + ${m:-0} ))
|
||||
}
|
||||
# 任意 "H:M"/"HH:MM" -> 规范 "HH:MM"
|
||||
_pad() {
|
||||
local h m
|
||||
h=$(_digits "${1%%:*}")
|
||||
m=$(_digits "${1#*:}")
|
||||
printf '%02d:%02d' "${h:-0}" "${m:-0}"
|
||||
}
|
||||
|
||||
# 把一个 [t_on, t_off] 窗口(H:M/HH:MM)转成 nft 时间匹配表达式(行),
|
||||
# 输出到全局变量 NFT_FRAGS。nft 的 meta hour 原生支持 "HH:MM"-"HH:MM"。
|
||||
# 统一语义:结束分钟视为完整包含(闭区间含其末秒),故上界一律补 :59 —
|
||||
# 非跨午夜 [t_on, t_off] -> "HH:MM"-"t_off:59";跨午夜拆 [t_on,23:59:59]+[00:00,t_off:59]。
|
||||
nft_split_window() {
|
||||
local t_on t_off
|
||||
NFT_FRAGS=""
|
||||
[ -n "$1" ] && [ -n "$2" ] || return 0
|
||||
t_on=$(_pad "$1"); t_off=$(_pad "$2")
|
||||
|
||||
if [ "$(_mins "$t_on")" -le "$(_mins "$t_off")" ]; then
|
||||
NFT_FRAGS="meta hour \"$t_on\"-\"$t_off:59\""
|
||||
else
|
||||
# 跨午夜:拆成 [on,23:59:59] + [00:00,off:59]
|
||||
NFT_FRAGS="meta hour \"$t_on\"-\"23:59:59\"
|
||||
meta hour \"00:00\"-\"$t_off:59\""
|
||||
fi
|
||||
}
|
||||
|
||||
# 统一执行 nft 规则命令并检查返回码。
|
||||
# 失败时记日志、立即回滚删表并置全局 NFT_FAIL=1(供调用方中止后续写入),
|
||||
# 使最终状态收敛为"完整规则表"或"空表",避免留下只 DROP 无 ACCEPT 的断网中间态。
|
||||
nft_rule() {
|
||||
# 已失败则短路,避免对已回滚删除的表继续逐条报错刷屏
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
"$@" 2>/dev/null || {
|
||||
logger -t miaplus "nft rule failed: $*"
|
||||
stop_nft
|
||||
NFT_FAIL=1
|
||||
}
|
||||
}
|
||||
|
||||
# 组合规则前缀:链决定是否只限 53 端口;ipv6enable=0 时限定 ipv4
|
||||
nft_prefix() {
|
||||
local chain=$1
|
||||
local pref=""
|
||||
[ "$chain" = "forward" ] || pref="meta l4proto { tcp, udp } th dport 53 "
|
||||
if [ "$ipv6enable" != "1" ]; then
|
||||
pref="meta nfproto ipv4 $pref"
|
||||
fi
|
||||
NFT_PREFIX="$pref"
|
||||
}
|
||||
|
||||
# 插入一条白名单 ACCEPT 规则(insert 到链首,兜底 DROP 在尾)
|
||||
# 参数:MAC day_term [链名=当前]
|
||||
nft_insert_accept() {
|
||||
local mac=$1 day=$2 chain=${3:-input}
|
||||
nft_prefix "$chain"
|
||||
local cond
|
||||
while IFS= read -r frag; do
|
||||
[ -n "$frag" ] || continue
|
||||
cond="$NFT_PREFIX ether saddr $mac $frag"
|
||||
[ -n "$day" ] && cond="$cond meta day $day"
|
||||
nft_rule nft insert rule inet miaplus $chain $cond accept
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
done <<EOF
|
||||
$NFT_FRAGS
|
||||
EOF
|
||||
}
|
||||
|
||||
# 插入一条兜底 DROP 规则(append 到链尾)
|
||||
# 参数:MAC [链名=当前]
|
||||
nft_add_drop() {
|
||||
local mac=$1 chain=${2:-input}
|
||||
nft_prefix "$chain"
|
||||
nft_rule nft add rule inet miaplus $chain $NFT_PREFIX ether saddr $mac drop
|
||||
}
|
||||
|
||||
# 需要写入的链列表:非严格=仅 input;严格=input+forward
|
||||
nft_target_chains() {
|
||||
if [ "${strict:-0}" -eq 1 ]; then
|
||||
echo "input forward"
|
||||
else
|
||||
echo "input"
|
||||
fi
|
||||
}
|
||||
|
||||
add_rule_nft(){
|
||||
local ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
|
||||
local macaddrs=$(uci_get_mac_by_template $1 '')
|
||||
if [ -z "$macaddrs" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local chains="$(nft_target_chains)"
|
||||
local macaddr i enable timeon timeoff z1 z2 z3 z4 z5 z6 z7
|
||||
|
||||
# 先为每个绑定 MAC 加兜底 DROP(每条链一次,不重复)
|
||||
for macaddr in $macaddrs; do
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
local ch
|
||||
for ch in $chains; do
|
||||
nft_add_drop "$macaddr" "$ch"
|
||||
done
|
||||
done
|
||||
|
||||
# 再按时间段插入 ACCEPT 白名单
|
||||
for i in $(seq 0 100); do
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
enable=$(uci_get_by_type $1 enable '' $i)
|
||||
timeon=$(uci_get_by_type $1 timeon '' $i)
|
||||
timeoff=$(uci_get_by_type $1 timeoff '' $i)
|
||||
z1=$(uci_get_by_type $1 z1 '' $i)
|
||||
z2=$(uci_get_by_type $1 z2 '' $i)
|
||||
z3=$(uci_get_by_type $1 z3 '' $i)
|
||||
z4=$(uci_get_by_type $1 z4 '' $i)
|
||||
z5=$(uci_get_by_type $1 z5 '' $i)
|
||||
z6=$(uci_get_by_type $1 z6 '' $i)
|
||||
z7=$(uci_get_by_type $1 z7 '' $i)
|
||||
|
||||
[ -n "$enable" ] || break
|
||||
[ -n "$timeon" ] || break
|
||||
[ -n "$timeoff" ] || break
|
||||
[ "$enable" = "1" ] || continue
|
||||
|
||||
local dayspec=$(nft_dayterm "$z1" "$z2" "$z3" "$z4" "$z5" "$z6" "$z7")
|
||||
|
||||
nft_split_window "$timeon" "$timeoff"
|
||||
for macaddr in $macaddrs; do
|
||||
local ch
|
||||
for ch in $chains; do
|
||||
nft_insert_accept "$macaddr" "$dayspec" "$ch"
|
||||
done
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
start_nft(){
|
||||
enable=$(uci -q get miaplus.@basic[0].enable)
|
||||
[ "${enable:-0}" -eq 0 ] && return 0
|
||||
strict=$(uci -q get miaplus.@basic[0].strict)
|
||||
local ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
|
||||
|
||||
NFT_FAIL=""
|
||||
|
||||
# 建表/建链也走 nft_rule,使最根本的"建表/建链失败"同样记日志并可回滚
|
||||
nft_rule nft add table inet miaplus
|
||||
nft_rule nft add chain inet miaplus input '{ type filter hook input priority 0; policy accept; }'
|
||||
nft_rule nft add chain inet miaplus prerouting '{ type nat hook prerouting priority dstnat; policy accept; }'
|
||||
# DNS 劫持遵循 ipv6enable:关闭时仅重定向 IPv4 的 53,与 filter 链/DROP 语义对齐
|
||||
if [ "${ipv6enable:-0}" -eq 1 ]; then
|
||||
nft_rule nft add rule inet miaplus prerouting meta l4proto { tcp, udp } th dport 53 redirect to :53
|
||||
else
|
||||
nft_rule nft add rule inet miaplus prerouting meta nfproto ipv4 meta l4proto { tcp, udp } th dport 53 redirect to :53
|
||||
fi
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
|
||||
# 严格模式:额外对 FORWARD 全流量做同样控制
|
||||
if [ "${strict:-0}" -eq 1 ]; then
|
||||
nft_rule nft add chain inet miaplus forward '{ type filter hook forward priority 0; policy accept; }'
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
fi
|
||||
|
||||
add_rules
|
||||
|
||||
# 任一规则写入失败:回滚整个表,避免留下只有 DROP、无 ACCEPT 白名单的断网状态
|
||||
if [ -n "$NFT_FAIL" ]; then
|
||||
logger -t miaplus "fw4 rules incomplete, rolling back"
|
||||
stop_nft
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
status_nft(){
|
||||
if nft list table inet miaplus >/dev/null 2>&1; then
|
||||
echo "running"
|
||||
return 0
|
||||
fi
|
||||
echo "not running"
|
||||
return 1
|
||||
}
|
||||
stop_nft(){
|
||||
nft delete table inet miaplus 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# 统一入口
|
||||
# =====================================================================
|
||||
start(){
|
||||
stop
|
||||
if have_fw4; then
|
||||
start_nft
|
||||
else
|
||||
start_ipt
|
||||
fi
|
||||
}
|
||||
status(){
|
||||
if have_fw4; then
|
||||
status_nft
|
||||
else
|
||||
status_ipt
|
||||
fi
|
||||
}
|
||||
stop(){
|
||||
if have_fw4; then
|
||||
stop_nft
|
||||
else
|
||||
stop_ipt
|
||||
fi
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
EEOF
|
||||
}
|
||||
|
||||
# fw4 与 fw3 共用 /etc/config/firewall 的 config include 机制;fw4_compatible
|
||||
# 让 fw4 在 reload 时执行用户脚本,fw3 忽略此未知 option(安全)
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@miaplus[-1]
|
||||
add ucitrack miaplus
|
||||
@@ -18,6 +20,7 @@ uci -q batch <<-EOF >/dev/null
|
||||
set firewall.miaplus.type=script
|
||||
set firewall.miaplus.path=/etc/miaplus.include
|
||||
set firewall.miaplus.reload=1
|
||||
set firewall.miaplus.fw4_compatible=1
|
||||
commit firewall
|
||||
EOF
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=baidudrive
|
||||
PKG_VERSION:=1.0.1
|
||||
PKG_RELEASE:=2
|
||||
PKG_VERSION:=1.0.2
|
||||
PKG_RELEASE:=3
|
||||
LUCI_DEPENDS:=+baidudrive +luci-compat
|
||||
LUCI_MINIFY_CSS:=0
|
||||
LUCI_MINIFY_JS:=0
|
||||
|
||||
@@ -8,11 +8,10 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=LuCI support for KSpeeder
|
||||
LUCI_DEPENDS:=+istoreenhance
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=0.6.6-r1
|
||||
PKG_VERSION:=0.6.6-r2
|
||||
# PKG_RELEASE MUST be empty for luci.mk
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=KaiPlus
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=4
|
||||
PKG_VERSION:=1.0.1
|
||||
PKG_RELEASE:=5
|
||||
LUCI_DEPENDS:=+kaiplus +luci-compat
|
||||
LUCI_MINIFY_CSS:=0
|
||||
LUCI_MINIFY_JS:=0
|
||||
|
||||
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 459 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 219 KiB |
@@ -0,0 +1,247 @@
|
||||
# MPTCP — User Guide
|
||||
|
||||
`luci-app-mptcp` is the LuCI front end for the kernel's Multipath TCP stack
|
||||
— the layer that actually bonds your WAN links together. It has seven pages
|
||||
under **Network → MPTCP**: **MPTCP** (settings), **Diagnostics**,
|
||||
**DSCP / Weight Routing**, **Bandwidth**, **Established connections**,
|
||||
**MPTCP Fullmesh**, and **MPTCP monitoring**.
|
||||
|
||||
Screenshots below were taken on a test router (`v0.64-snapshot`, kernel
|
||||
6.18, 3 WAN links). Peer/remote IPs that would reveal the test VPS were
|
||||
replaced with a placeholder (`203.0.113.10`) before capture — everything
|
||||
else is unmodified live output.
|
||||
|
||||
## MPTCP (settings)
|
||||
|
||||
```
|
||||
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp
|
||||
```
|
||||
|
||||

|
||||
|
||||
The form's contents adapt to your kernel version — the screenshot is from
|
||||
a 6.18 kernel, which exposes the modern in-kernel/userspace path-manager
|
||||
options; older kernels (<6) show a different, longer set of legacy fields
|
||||
(scheduler-specific tuning like ndiffports/round-robin subflow counts,
|
||||
`mptcp_version`, etc.) instead.
|
||||
|
||||
### GLOBALS
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| **Multipath TCP** | Read-only indicator of whether MPTCP is enabled network-wide. |
|
||||
| **Multipath TCP checksum** | Enables MPTCP-level checksums (extra integrity check, minor overhead). |
|
||||
| **Multipath TCP path-manager** | `default` or `fullmesh` (OMR's normal choice — creates subflows between every local/remote address pair). |
|
||||
| **Multipath TCP scheduler** | Which subflow the kernel picks to send data on. `default`, or a BPF scheduler (`bpf_burst`, `bpf_red`, `bpf_first`, `bpf_rr`, plus any custom `.o` dropped into `/usr/share/bpf/scheduler` — auto-discovered and listed here). On kernels <6, classic in-tree schedulers (round-robin, redundant, BLEST, ECF) are offered instead. |
|
||||
| **Congestion Control** | Populated live from `sysctl net.ipv4.tcp_available_congestion_control` — pick any congestion control algorithm your kernel has compiled in. Default is `cubic`; this bench uses `bbr`. |
|
||||
| **Path Manager type** *(kernel ≥6 only)* | In-kernel (simpler, default) vs. userspace (delegates subflow decisions to `mptcpd`). |
|
||||
| **Max subflows** | Cap on additional subflows per MPTCP connection — usually matches (or exceeds) your WAN count. |
|
||||
| **Retranmission intervals** | How many MPTCP-level retransmit intervals with no traffic/no ack before a subflow is declared *stale*. Lower = faster active-backup switchover; higher = better utilization on lossy/high-BER links. |
|
||||
| **Max add address** | Cap on `ADD_ADDR` suboptions accepted per connection. |
|
||||
| **Control message timeout** | Resend delay for an unacknowledged `ADD_ADDR` message. |
|
||||
| **Blackhole timeout** *(kernel ≥6.18)* | Initial cooldown before re-enabling MPTCP on a socket after a middlebox appears to be blackholing it; grows exponentially on repeat failures. |
|
||||
| **Close timeout** *(kernel ≥6.18)* | Make-after-break grace period: how long a socket holds state after its last subflow drops, before moving to `TCP_CLOSE`. |
|
||||
| **Control message timeout** *(second one, kernel ≥6.18)* | SYN+MP_CAPABLE retransmit count before falling back to plain TCP. |
|
||||
|
||||
When the userspace path manager is selected, extra fields appear
|
||||
(`mptcpd` enable/disable, its plugin and path-manager `.so` lists —
|
||||
auto-discovered from `/usr/lib/mptcpd` — and address announcement/
|
||||
notification flags); not shown here since this bench uses the in-kernel
|
||||
manager.
|
||||
|
||||
### Interfaces Settings
|
||||
|
||||
One block per real network interface (LAN + every WAN — VPN-internal
|
||||
interfaces like `omrvpn`/`omr6in4` are filtered out here since they aren't
|
||||
independent WAN paths):
|
||||
|
||||
- **Multipath TCP** — `disabled`, `enabled`, `master`, or `backup`.
|
||||
Exactly one interface must be `master` (the interface MPTCP's initial
|
||||
subflow uses); the rest are `enabled` (or `backup`, only cut in when
|
||||
masters/enabled links fail).
|
||||
- **Weight** — only used when a `*weight` scheduler/path-manager is
|
||||
active; >100 makes a link more attractive, <100 less, up to 256.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
```
|
||||
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_diagnostics
|
||||
```
|
||||
|
||||
Answers *why isn't MPTCP aggregating my WANs correctly?* without requiring
|
||||
you to read raw `nstat`/`ip mptcp` output by hand. Combines the kernel's
|
||||
MPTCP MIB counters, the live endpoint/subflow state, and a per-WAN sanity
|
||||
check into a plain-English, color-coded issue list (fallback to plain TCP,
|
||||
blackholed/stale subflows, resets, checksum errors, JOIN handshake
|
||||
failures, dropped ADD_ADDR/RM_ADDR signalling), refreshed every 15 seconds.
|
||||
|
||||

|
||||
|
||||
On this bench, the issue list actually fired: an orange banner flagging
|
||||
*"2644 MPTCP-level reset/fastclose event(s) sent by this router (~119/hour
|
||||
since boot)"* — with its own plain-English caveat underneath explaining
|
||||
that a high count sent *by the router itself* (as opposed to received) is
|
||||
usually just abortive closes from its own health-check/keepalive probes
|
||||
opening a connection, sending one request, then closing it, and is only
|
||||
worth escalating if paired with rising Blackhole/SubflowStale counters —
|
||||
which is exactly the kind of self-diagnosis this page exists to save you
|
||||
from having to work out by hand.
|
||||
|
||||
A **Per-WAN MPTCP endpoint status** table cross-checks every
|
||||
multipath-enabled interface against `ip mptcp endpoint show`: a WAN can
|
||||
look perfectly healthy in OpenMPTCProuter's Status page (IP, gateway, ping
|
||||
all fine) and still never carry a subflow if its endpoint is missing —
|
||||
this is usually the fastest way to catch that.
|
||||
|
||||
A **Live MPTCP subflows** table goes one level deeper than that: a live
|
||||
read of every established subflow (preferring `omr-sockdiag`, a small tool
|
||||
that queries the kernel directly over `NETLINK_SOCK_DIAG` — the same
|
||||
INET_DIAG protocol `ss` uses internally — and falling back to parsing
|
||||
`ss -tin` text if that tool isn't installed), one row per actual established
|
||||
subflow, attributed to its WAN by matching the connection's local address
|
||||
against the endpoint list above. Each row shows the local/remote
|
||||
address:port, whether the kernel is
|
||||
currently using it as an active or backup path, congestion window, RTT
|
||||
(smoothed/variance), retransmissions (current/total), and pacing/delivery
|
||||
rate — the same live TCP-level numbers `ss -i` would show for that
|
||||
connection, without needing to run it by hand over SSH. A WAN with a
|
||||
correctly-registered endpoint but no row here means the path manager
|
||||
just hasn't opened a subflow on it yet (or it's currently down/unreachable),
|
||||
which the endpoint table alone can't tell you.
|
||||
|
||||
A **Kernel MPTCP
|
||||
configuration** block shows the live scheduler/path-manager/checksum/
|
||||
timeout values straight from `/proc/sys/net/mptcp/*`, and a sortable
|
||||
counters table plus collapsible raw `multipath -k/-f/-c` sections are
|
||||
available for anyone who wants the unprocessed data (the raw `-k`/`-f`/`-c`
|
||||
sections mirror **MPTCP monitoring**'s kernel-info line, **MPTCP
|
||||
Fullmesh**, and **Established connections** below).
|
||||
|
||||
The counters table is genuinely cumulative since boot, unlike **MPTCP
|
||||
monitoring**'s raw `multipath -m` output below: that wraps `nstat -z`,
|
||||
which (without `-a`) reports the delta since the *previous* `nstat` call by
|
||||
anyone on the router — confirmed live, two calls a couple seconds apart
|
||||
came back ~0. A page auto-polling every 15s through that would show
|
||||
"since last poll" numbers while looking like a lifetime total, and would
|
||||
race with anything else on the box that also calls `nstat`. Diagnostics
|
||||
reads the kernel's own procfs SNMP-style counters directly instead
|
||||
(`/proc/net/netstat`'s `MPTcpExt:` block, or `/proc/net/mptcp_net/snmp` on
|
||||
older kernels), which has no such history-file/shared-state gotcha.
|
||||
|
||||
Backed by a new method on the same `luci.mptcp` rpcd object as the other
|
||||
pages: `ubus call luci.mptcp diagnose '{}'` (no arguments).
|
||||
|
||||
## DSCP / Weight Routing
|
||||
|
||||
```
|
||||
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_dscp_routing
|
||||
```
|
||||
|
||||
A per-WAN pin/weight table shared between two independent consumers —
|
||||
MPTCP's `bpf_dscp`/`bpf_weight*` schedulers (see the MPTCP settings page
|
||||
above) and `mqvpn`'s equivalent scheduler modes — so both can be
|
||||
configured from one place instead of duplicating the same WAN list twice.
|
||||
This page only edits the pins/weights themselves; which scheduler is
|
||||
actually *active* is still chosen on the MPTCP or MQVPN settings pages.
|
||||
|
||||
Its content is entirely conditional: it shows the **DSCP class pins**
|
||||
table only if MPTCP's scheduler is `bpf_dscp` *or* mqvpn is enabled at
|
||||
all (mqvpn pushes its DSCP mask unconditionally whenever it's on, unlike
|
||||
weight below), and the **WAN weight** section only if MPTCP's scheduler
|
||||
is one of the `bpf_weight`/`bpf_weight_rr`/`bpf_burstweight` family *or*
|
||||
mqvpn's scheduler is `wrtt`/`wrr`. With neither condition met, the page
|
||||
just says so and renders nothing else.
|
||||
|
||||

|
||||
|
||||
On this bench, MPTCP's scheduler is `blest` (neither DSCP nor weight) but
|
||||
mqvpn is enabled, so only the DSCP half shows:
|
||||
|
||||
| Section | Fields |
|
||||
|---|---|
|
||||
| **Gateway sync** | **Mirror DSCP/weight pins to gateway** — also push each pin/weight to the VPS so it holds for download traffic too (the gateway's own send direction), not just what the router sends. Disabling it only stops *future* syncs; pins already pushed stay pushed. Its *saved* value (not the live checkbox) decides whether the Download column below even renders — toggling it needs Save & Apply and a reload before that column appears/disappears. |
|
||||
| **DSCP class pins** | One row per DSCP class → **Upload interface** (router → internet: pins the class to a WAN for `bpf_dscp`'s local map, and for mqvpn announces it on that path's DSCP mask via the control API) and, only while Gateway sync is on, **Download interface** (gateway → router; `bpf_dscp`-only, pins the VPS's own send-side choice — leave empty to just mirror the Upload interface instead). mqvpn has no separate download pin at all — its downlink always mirrors upload. |
|
||||
| **WAN weight** *(not shown here — no weight scheduler active)* | One row per multipath-enabled WAN, a single **Weight** value (default 100, max 256) used by both upload and download, for both MPTCP's `*weight` schedulers and mqvpn's Weighted RTT/Weighted Round Robin. |
|
||||
|
||||
## Bandwidth
|
||||
|
||||
```
|
||||
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/bandwidth
|
||||
```
|
||||
|
||||
Live, polled-every-second SVG traffic graphs. A tab per multipath-enabled
|
||||
interface, plus an **All interfaces** overview.
|
||||
|
||||

|
||||
|
||||
The combined view overlays every WAN's inbound (and, separately, outbound)
|
||||
throughput plus a bold total line, with a live current/average/peak table
|
||||
underneath per interface and for the total.
|
||||
|
||||

|
||||
|
||||
Selecting a WAN tab (e.g. `wan1`) narrows this to that link alone —
|
||||
inbound and outbound charts with their own current/average/peak stats,
|
||||
useful for judging one link's real-world throughput independent of the
|
||||
others.
|
||||
|
||||
## Established connections
|
||||
|
||||
```
|
||||
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_connections
|
||||
```
|
||||
|
||||

|
||||
|
||||
Raw, auto-refreshing (every 10s) output of `multipath -c` — every ESTAB
|
||||
MPTCP connection with local and peer address:port. Useful for confirming
|
||||
traffic is actually using multiple subflows/paths, or for spotting
|
||||
connections stuck on a single WAN.
|
||||
|
||||
## MPTCP Fullmesh
|
||||
|
||||
```
|
||||
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_fullmesh
|
||||
```
|
||||
|
||||

|
||||
|
||||
Raw output of `multipath -f`: the fullmesh path-manager's current subflow
|
||||
map — one line per local address, its subflow ID, and which device it
|
||||
rides on. This is the direct evidence that the fullmesh path-manager
|
||||
picked up every WAN address correctly; a WAN missing from this list isn't
|
||||
being used as an MPTCP subflow.
|
||||
|
||||
## MPTCP monitoring
|
||||
|
||||
```
|
||||
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_monitor
|
||||
```
|
||||
|
||||

|
||||
|
||||
Raw output of `multipath -m`: the kernel's full `MPTcpExt*` counter dump
|
||||
(from `nstat`/`netstat -s`-style MIB counters) — join attempts/failures,
|
||||
retransmissions, checksum errors, address add/remove events, fallbacks to
|
||||
plain TCP, etc. All zero on a healthy, idle test link; non-zero values in
|
||||
categories like `MPTcpExtMPCapableFallbackSYNACK` or `MPTcpExtMPFailTx`
|
||||
are what you'd check first when MPTCP negotiation is suspected to be
|
||||
failing (e.g. a middlebox stripping MPTCP TCP options).
|
||||
|
||||
## Not reachable from the menu: MPTCP Support Check
|
||||
|
||||
The package also ships `mptcp/mptcp_check.js` — a page that runs a
|
||||
`tracebox`-based trace toward the configured VPS over a chosen interface,
|
||||
to show whether MPTCP options survive the path (look for
|
||||
`TCPOptionMPTCPCapable [...] Sender's Key` in the output: present means
|
||||
supported, a leading `-` means something on the path is stripping it).
|
||||
It calls the same `luci.mptcp` rpcd backend as the other pages
|
||||
(`mptcp_check_trace`, wrapping `tracebox -s omr-mptcp-trace.lua`).
|
||||
|
||||
However, `luci-app-mptcp.json` has no menu entry for it, and LuCI's
|
||||
dispatcher falls back to the MPTCP settings page for its URL
|
||||
(`admin/network/mptcp/mptcp_check`) rather than routing to it — so as
|
||||
shipped, this page isn't actually reachable through the web UI. Worth
|
||||
knowing if you're debugging why MPTCP isn't negotiating: the check logic
|
||||
exists and works via the same rpcd method, it just currently has no way in
|
||||
from the menu.
|
||||
@@ -1,15 +1,13 @@
|
||||
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI support for OAF
|
||||
LUCI_TITLE:=LuCI app for OAF
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_DEPENDS:=+appfilter +kmod-oaf +luci-compat
|
||||
LUCI_DEPENDS:=+appfilter +kmod-oaf +luci-compat +luci-lib-jsonc
|
||||
|
||||
PKG_NAME:=luci-app-oaf
|
||||
PKG_VERSION:=6.1.4
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=7.0
|
||||
PKG_RELEASE:=4
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |