mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 18:03:56 +08:00
🦄 Sync 2026-09-21 17:33:30
This commit is contained in:
1 parent
d733dbc21e
commit
385706139a
23 files changed
+831
-202
No files matched your search
@@ -8,8 +8,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=apfree-wifidog
|
PKG_NAME:=apfree-wifidog
|
||||||
PKG_VERSION:=9.09.2938
|
PKG_VERSION:=9.09.2947
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/liudf0716/$(PKG_NAME)/tar.gz/$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/liudf0716/$(PKG_NAME)/tar.gz/$(PKG_VERSION)?
|
||||||
|
|||||||
+3
-3
@@ -5,10 +5,10 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=dae
|
PKG_NAME:=dae
|
||||||
PKG_VERSION:=2026.09.18
|
PKG_VERSION:=2026.09.20
|
||||||
PKG_RELEASE:=45
|
PKG_RELEASE:=46
|
||||||
|
|
||||||
PKG_SOURCE:=dae-src-2026.09.18-dc29e60bab23.tar.gz
|
PKG_SOURCE:=dae-src-2026.09.20-2a846ee5a7e4.tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
|
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|||||||
@@ -5,15 +5,20 @@
|
|||||||
KPROBE_SKB_AT(3)
|
KPROBE_SKB_AT(3)
|
||||||
KPROBE_SKB_AT(4)
|
KPROBE_SKB_AT(4)
|
||||||
-KPROBE_SKB_AT(5)
|
-KPROBE_SKB_AT(5)
|
||||||
|
|
||||||
SEC("kprobe/skb_lifetime_termination")
|
SEC("kprobe/skb_lifetime_termination")
|
||||||
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
|
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
|
||||||
--- a/trace/trace.go
|
--- a/trace/trace.go
|
||||||
+++ b/trace/trace.go
|
+++ b/trace/trace.go
|
||||||
@@ -191,7 +191,6 @@ func attachBpfToTargets(objs *bpfObjects, targets map[string]int) (links []link.Link, err error) {
|
@@ -191,11 +191,10 @@
|
||||||
1: objs.KprobeSkb1,
|
progByPos := map[int]*ebpf.Program{
|
||||||
2: objs.KprobeSkb2,
|
1: objs.KprobeSkb1,
|
||||||
3: objs.KprobeSkb3,
|
2: objs.KprobeSkb2,
|
||||||
4: objs.KprobeSkb4,
|
3: objs.KprobeSkb3,
|
||||||
|
4: objs.KprobeSkb4,
|
||||||
- 5: objs.KprobeSkb5,
|
- 5: objs.KprobeSkb5,
|
||||||
}
|
}
|
||||||
|
symbolsByPos := make(map[int][]string, len(progByPos))
|
||||||
|
for fn, pos := range targets {
|
||||||
|
if _, ok := progByPos[pos]; !ok {
|
||||||
|
continue
|
||||||
+3
-3
@@ -5,10 +5,10 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=daed
|
PKG_NAME:=daed
|
||||||
PKG_VERSION:=2026.09.18
|
PKG_VERSION:=2026.09.20
|
||||||
PKG_RELEASE:=56
|
PKG_RELEASE:=57
|
||||||
|
|
||||||
PKG_SOURCE:=daed-src-2026.09.18-0d4c9d0c95b9.tar.gz
|
PKG_SOURCE:=daed-src-2026.09.20-d00211e69e94.tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|||||||
@@ -5,15 +5,20 @@
|
|||||||
KPROBE_SKB_AT(3)
|
KPROBE_SKB_AT(3)
|
||||||
KPROBE_SKB_AT(4)
|
KPROBE_SKB_AT(4)
|
||||||
-KPROBE_SKB_AT(5)
|
-KPROBE_SKB_AT(5)
|
||||||
|
|
||||||
SEC("kprobe/skb_lifetime_termination")
|
SEC("kprobe/skb_lifetime_termination")
|
||||||
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
|
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
|
||||||
--- a/dae-core/trace/trace.go
|
--- a/dae-core/trace/trace.go
|
||||||
+++ b/dae-core/trace/trace.go
|
+++ b/dae-core/trace/trace.go
|
||||||
@@ -191,7 +191,6 @@ func attachBpfToTargets(objs *bpfObjects, targets map[string]int) (links []link.Link, err error) {
|
@@ -191,11 +191,10 @@
|
||||||
1: objs.KprobeSkb1,
|
progByPos := map[int]*ebpf.Program{
|
||||||
2: objs.KprobeSkb2,
|
1: objs.KprobeSkb1,
|
||||||
3: objs.KprobeSkb3,
|
2: objs.KprobeSkb2,
|
||||||
4: objs.KprobeSkb4,
|
3: objs.KprobeSkb3,
|
||||||
|
4: objs.KprobeSkb4,
|
||||||
- 5: objs.KprobeSkb5,
|
- 5: objs.KprobeSkb5,
|
||||||
}
|
}
|
||||||
|
symbolsByPos := make(map[int][]string, len(progByPos))
|
||||||
|
for fn, pos := range targets {
|
||||||
|
if _, ok := progByPos[pos]; !ok {
|
||||||
|
continue
|
||||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
LUCI_TITLE:=AgentFlow
|
LUCI_TITLE:=AgentFlow
|
||||||
PKG_VERSION:=1.0.1
|
PKG_VERSION:=1.0.1
|
||||||
PKG_RELEASE:=6
|
PKG_RELEASE:=7
|
||||||
LUCI_DEPENDS:=+agentflow +luci-compat +luci-lib-taskd
|
LUCI_DEPENDS:=+agentflow +luci-compat +luci-lib-taskd
|
||||||
LUCI_MINIFY_CSS:=0
|
LUCI_MINIFY_CSS:=0
|
||||||
LUCI_MINIFY_JS:=0
|
LUCI_MINIFY_JS:=0
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ module("luci.controller.agentflow", package.seeall)
|
|||||||
local APPS_PROXY_PREFIX = "/apps=http://127.0.0.1:19290"
|
local APPS_PROXY_PREFIX = "/apps=http://127.0.0.1:19290"
|
||||||
local DEFAULT_BASE_PATH = "/apps/agentflow/"
|
local DEFAULT_BASE_PATH = "/apps/agentflow/"
|
||||||
local DEFAULT_PORT = 9000
|
local DEFAULT_PORT = 9000
|
||||||
|
local AGENTS = {
|
||||||
|
{ id = "codexcli", package = "@openai/codex" },
|
||||||
|
{ id = "claude-code", package = "@anthropic-ai/claude-code" },
|
||||||
|
{ id = "opencode", package = "opencode-ai" },
|
||||||
|
{ id = "kimi", package = "@moonshot-ai/kimi-code" },
|
||||||
|
{ id = "reasonix", package = "reasonix" }
|
||||||
|
}
|
||||||
|
|
||||||
function index()
|
function index()
|
||||||
entry({"admin", "services", "agentflow_status"}, call("agentflow_status"))
|
entry({"admin", "services", "agentflow_status"}, call("agentflow_status"))
|
||||||
@@ -133,10 +140,54 @@ local function agentflow_entry_url()
|
|||||||
return "http://" .. url_authority(request_or_lan_host(), port) .. base_path
|
return "http://" .. url_authority(request_or_lan_host(), port) .. base_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function node_modules_root()
|
||||||
|
local sys = require "luci.sys"
|
||||||
|
local util = require "luci.util"
|
||||||
|
local script = table.concat({
|
||||||
|
'. /lib/functions/mise.sh 2>/dev/null || exit 1',
|
||||||
|
'istore_runtime_env >/dev/null 2>&1 || exit 1',
|
||||||
|
'mise_bin="$(command -v mise-istore || command -v mise)"',
|
||||||
|
'[ -n "$mise_bin" ] || exit 1',
|
||||||
|
'node_dir="$("$mise_bin" where node@lts 2>/dev/null)"',
|
||||||
|
'[ -n "$node_dir" ] || exit 1',
|
||||||
|
'printf "%s/lib/node_modules" "$node_dir"'
|
||||||
|
}, "; ")
|
||||||
|
local root = sys.exec("/bin/sh -c " .. util.shellquote(script)) or ""
|
||||||
|
root = root:match("^%s*(.-)%s*$")
|
||||||
|
if root == "" or root:sub(1, 1) ~= "/" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return root
|
||||||
|
end
|
||||||
|
|
||||||
|
local function agent_statuses()
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
local root = node_modules_root()
|
||||||
|
local statuses = {}
|
||||||
|
|
||||||
|
for _, agent in ipairs(AGENTS) do
|
||||||
|
local status = { id = agent.id, installed = false }
|
||||||
|
if root then
|
||||||
|
local package_file = root .. "/" .. agent.package .. "/package.json"
|
||||||
|
local package_data = fs.readfile(package_file)
|
||||||
|
local package_json = package_data and jsonc.parse(package_data) or nil
|
||||||
|
if type(package_json) == "table" then
|
||||||
|
status.installed = true
|
||||||
|
status.version = package_json.version
|
||||||
|
end
|
||||||
|
end
|
||||||
|
statuses[#statuses + 1] = status
|
||||||
|
end
|
||||||
|
|
||||||
|
return statuses, root ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
function agentflow_status()
|
function agentflow_status()
|
||||||
local sys = require "luci.sys"
|
local sys = require "luci.sys"
|
||||||
local port, base_path = agentflow_config()
|
local port, base_path = agentflow_config()
|
||||||
local entry_url = agentflow_entry_url()
|
local entry_url = agentflow_entry_url()
|
||||||
|
local agents, agents_available = agent_statuses()
|
||||||
|
|
||||||
local status = {
|
local status = {
|
||||||
running = (sys.call("pidof agentflow >/dev/null") == 0),
|
running = (sys.call("pidof agentflow >/dev/null") == 0),
|
||||||
@@ -145,7 +196,9 @@ function agentflow_status()
|
|||||||
entry_url = entry_url,
|
entry_url = entry_url,
|
||||||
proxy_prefix_supported = uhttpd_supports_proxy_prefix(),
|
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()
|
linkeasefull_running = linkeasefull_running(),
|
||||||
|
agents_available = agents_available,
|
||||||
|
agents = agents
|
||||||
}
|
}
|
||||||
write_json(status)
|
write_json(status)
|
||||||
end
|
end
|
||||||
@@ -158,20 +211,17 @@ function agentflow_agent_install()
|
|||||||
local task_id = "agentflow-agent-install"
|
local task_id = "agentflow-agent-install"
|
||||||
local task_script = "/tmp/agentflow-agent-install.sh"
|
local task_script = "/tmp/agentflow-agent-install.sh"
|
||||||
local installer_url = "https://fw.koolcenter.com/binary/geili/agentflow/releases/installapp/installapp-mise.sh"
|
local installer_url = "https://fw.koolcenter.com/binary/geili/agentflow/releases/installapp/installapp-mise.sh"
|
||||||
local agents = {
|
local supported_agents = {}
|
||||||
codexcli = true,
|
for _, supported_agent in ipairs(AGENTS) do
|
||||||
["claude-code"] = true,
|
supported_agents[supported_agent.id] = true
|
||||||
opencode = true,
|
end
|
||||||
kimi = true,
|
|
||||||
reasonix = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if not require_post_csrf() then
|
if not require_post_csrf() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local agent = http.formvalue("agent") or ""
|
local agent = http.formvalue("agent") or ""
|
||||||
if not agents[agent] then
|
if not supported_agents[agent] then
|
||||||
write_json({ ok = false, error = "unsupported agent" })
|
write_json({ ok = false, error = "unsupported agent" })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,58 +1,378 @@
|
|||||||
<%+tasks/embed%>
|
<%+tasks/embed%>
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.agentflow-actions { display:flex; gap:10px; flex-wrap:wrap; margin:12px; }
|
.agentflow-shell {
|
||||||
.agentflow-actions .btn, .agentflow-agent-dialog-actions .btn { min-height:40px; }
|
--af-panel:#fff;
|
||||||
.agentflow-agent-modal { position:fixed; inset:0; z-index:10000; display:none; align-items:center; justify-content:center; padding:20px; box-sizing:border-box; background:rgba(15,23,42,.45); }
|
--af-panel-soft:#f6f8fc;
|
||||||
|
--af-border:#dfe5ef;
|
||||||
|
--af-text:#17213d;
|
||||||
|
--af-muted:#667397;
|
||||||
|
--af-primary:#356cf6;
|
||||||
|
--af-primary-hover:#2559d8;
|
||||||
|
--af-success:#159447;
|
||||||
|
--af-success-soft:#e9f8ef;
|
||||||
|
--af-shadow:0 8px 24px rgba(46, 67, 112, .08);
|
||||||
|
color:var(--af-text);
|
||||||
|
margin:12px 0 20px;
|
||||||
|
}
|
||||||
|
.agentflow-panel {
|
||||||
|
background:var(--af-panel);
|
||||||
|
border:1px solid var(--af-border);
|
||||||
|
border-radius:10px;
|
||||||
|
box-shadow:var(--af-shadow);
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
.agentflow-overview {
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns:minmax(260px, .9fr) minmax(320px, 1.1fr);
|
||||||
|
gap:28px;
|
||||||
|
padding:24px;
|
||||||
|
}
|
||||||
|
.agentflow-service {
|
||||||
|
display:flex;
|
||||||
|
align-items:flex-start;
|
||||||
|
gap:16px;
|
||||||
|
}
|
||||||
|
.agentflow-service-icon {
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
width:54px;
|
||||||
|
height:54px;
|
||||||
|
flex:0 0 54px;
|
||||||
|
border-radius:50%;
|
||||||
|
background:#eef1f6;
|
||||||
|
color:var(--af-muted);
|
||||||
|
font-size:28px;
|
||||||
|
font-weight:700;
|
||||||
|
}
|
||||||
|
.agentflow-service-icon.running {
|
||||||
|
background:var(--af-success-soft);
|
||||||
|
color:var(--af-success);
|
||||||
|
}
|
||||||
|
.agentflow-service-copy { min-width:0; }
|
||||||
|
.agentflow-service-copy h3 {
|
||||||
|
margin:2px 0 6px;
|
||||||
|
color:var(--af-text);
|
||||||
|
font-size:20px;
|
||||||
|
line-height:1.3;
|
||||||
|
}
|
||||||
|
.agentflow-service-copy p {
|
||||||
|
margin:0 0 16px;
|
||||||
|
color:var(--af-muted);
|
||||||
|
line-height:1.6;
|
||||||
|
}
|
||||||
|
.agentflow-access {
|
||||||
|
padding-left:28px;
|
||||||
|
border-left:1px solid var(--af-border);
|
||||||
|
}
|
||||||
|
.agentflow-access label {
|
||||||
|
display:block;
|
||||||
|
margin:0 0 9px;
|
||||||
|
font-weight:700;
|
||||||
|
}
|
||||||
|
.agentflow-address-row {
|
||||||
|
display:flex;
|
||||||
|
gap:8px;
|
||||||
|
}
|
||||||
|
.agentflow-address-row input[type="text"] {
|
||||||
|
min-width:0;
|
||||||
|
flex:1 1 auto;
|
||||||
|
height:40px;
|
||||||
|
box-sizing:border-box;
|
||||||
|
border:1px solid var(--af-border);
|
||||||
|
border-radius:7px;
|
||||||
|
background:var(--af-panel-soft);
|
||||||
|
color:var(--af-text);
|
||||||
|
font-variant-numeric:tabular-nums;
|
||||||
|
}
|
||||||
|
.agentflow-access-note {
|
||||||
|
min-height:20px;
|
||||||
|
margin:10px 0 0;
|
||||||
|
color:var(--af-muted);
|
||||||
|
font-size:13px;
|
||||||
|
}
|
||||||
|
.agentflow-button {
|
||||||
|
min-height:40px;
|
||||||
|
box-sizing:border-box;
|
||||||
|
border-radius:7px !important;
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
.agentflow-primary {
|
||||||
|
border-color:var(--af-primary) !important;
|
||||||
|
background:var(--af-primary) !important;
|
||||||
|
color:#fff !important;
|
||||||
|
}
|
||||||
|
.agentflow-primary:hover { background:var(--af-primary-hover) !important; }
|
||||||
|
.agentflow-button:disabled { cursor:not-allowed; opacity:.55; }
|
||||||
|
.agentflow-agents { margin-top:16px; padding:22px 20px 20px; }
|
||||||
|
.agentflow-section-heading {
|
||||||
|
display:flex;
|
||||||
|
align-items:flex-start;
|
||||||
|
gap:12px;
|
||||||
|
margin-bottom:18px;
|
||||||
|
}
|
||||||
|
.agentflow-section-mark {
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
width:38px;
|
||||||
|
height:38px;
|
||||||
|
flex:0 0 38px;
|
||||||
|
border-radius:9px;
|
||||||
|
background:#edf3ff;
|
||||||
|
color:var(--af-primary);
|
||||||
|
font-size:17px;
|
||||||
|
font-weight:800;
|
||||||
|
}
|
||||||
|
.agentflow-section-heading h3 { margin:0 0 4px; color:var(--af-text); font-size:20px; }
|
||||||
|
.agentflow-section-heading p { margin:0; color:var(--af-muted); line-height:1.55; }
|
||||||
|
.agentflow-agent-grid {
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns:repeat(3, minmax(0, 1fr));
|
||||||
|
gap:14px;
|
||||||
|
}
|
||||||
|
.agentflow-agent-card {
|
||||||
|
display:flex;
|
||||||
|
min-width:0;
|
||||||
|
min-height:190px;
|
||||||
|
padding:16px;
|
||||||
|
box-sizing:border-box;
|
||||||
|
flex-direction:column;
|
||||||
|
border:1px solid var(--af-border);
|
||||||
|
border-radius:9px;
|
||||||
|
background:var(--af-panel);
|
||||||
|
}
|
||||||
|
.agentflow-agent-card[data-state="installed"] { border-color:#b8dfc6; }
|
||||||
|
.agentflow-agent-top { display:flex; align-items:flex-start; gap:12px; }
|
||||||
|
.agentflow-agent-logo {
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
width:48px;
|
||||||
|
height:48px;
|
||||||
|
flex:0 0 48px;
|
||||||
|
border-radius:9px;
|
||||||
|
background:var(--agent-color, var(--af-primary));
|
||||||
|
color:#fff;
|
||||||
|
font-size:21px;
|
||||||
|
font-weight:800;
|
||||||
|
}
|
||||||
|
.agentflow-agent-copy { min-width:0; }
|
||||||
|
.agentflow-agent-title { margin:2px 0 4px; color:var(--af-text); font-size:17px; font-weight:700; line-height:1.4; }
|
||||||
|
.agentflow-agent-copy p { margin:0; color:var(--af-muted); font-size:13px; line-height:1.45; }
|
||||||
|
.agentflow-agent-meta {
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:10px;
|
||||||
|
margin:17px 0 12px;
|
||||||
|
color:var(--af-muted);
|
||||||
|
font-size:13px;
|
||||||
|
}
|
||||||
|
.agentflow-agent-state { display:inline-flex; align-items:center; gap:7px; font-weight:700; }
|
||||||
|
.agentflow-agent-dot { width:8px; height:8px; border-radius:50%; background:#8a95af; }
|
||||||
|
.agentflow-agent-card[data-state="installed"] .agentflow-agent-state { color:var(--af-success); }
|
||||||
|
.agentflow-agent-card[data-state="installed"] .agentflow-agent-dot { background:var(--af-success); box-shadow:0 0 0 4px var(--af-success-soft); }
|
||||||
|
.agentflow-agent-divider { color:#b4bdd0; }
|
||||||
|
.agentflow-install-button { width:100%; margin-top:auto; }
|
||||||
|
.agentflow-agent-card[data-state="installed"] .agentflow-install-button {
|
||||||
|
border-color:var(--af-success) !important;
|
||||||
|
background:var(--af-success-soft) !important;
|
||||||
|
color:var(--af-success) !important;
|
||||||
|
}
|
||||||
|
.agentflow-agent-modal {
|
||||||
|
position:fixed;
|
||||||
|
inset:0;
|
||||||
|
z-index:10000;
|
||||||
|
display:none;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
padding:20px;
|
||||||
|
box-sizing:border-box;
|
||||||
|
background:rgba(15, 23, 42, .48);
|
||||||
|
}
|
||||||
.agentflow-agent-modal.open { display:flex; }
|
.agentflow-agent-modal.open { display:flex; }
|
||||||
.agentflow-agent-dialog, body[theme="light"] .agentflow-agent-dialog { width:min(520px, 100%); padding:20px; box-sizing:border-box; border:1px solid #d0d7de; border-radius:12px; background:#fff; color:inherit; box-shadow:0 18px 48px rgba(15,23,42,.24); }
|
.agentflow-agent-dialog {
|
||||||
.agentflow-agent-dialog h3 { margin:0 0 8px; text-wrap:balance; }
|
width:100%;
|
||||||
.agentflow-agent-dialog > p, body[theme="light"] .agentflow-agent-dialog > p { margin:0 0 16px; color:#666; text-wrap:pretty; }
|
max-width:460px;
|
||||||
.agentflow-agent-field label { display:block; margin-bottom:6px; font-weight:600; }
|
padding:22px;
|
||||||
.agentflow-agent-field select { width:100%; min-height:40px; box-sizing:border-box; }
|
box-sizing:border-box;
|
||||||
.agentflow-agent-dialog-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:18px; }
|
border:1px solid var(--af-border);
|
||||||
body[theme="dark"] .agentflow-agent-dialog { border-color:#465166; background:#1f2937; color:#e5e7eb; }
|
border-radius:10px;
|
||||||
body[theme="dark"] .agentflow-agent-dialog > p { color:#aeb8c7; }
|
background:var(--af-panel);
|
||||||
|
color:var(--af-text);
|
||||||
|
box-shadow:0 18px 48px rgba(15, 23, 42, .24);
|
||||||
|
}
|
||||||
|
.agentflow-agent-dialog h3 { margin:0 0 8px; color:var(--af-text); }
|
||||||
|
.agentflow-agent-dialog p { margin:0; color:var(--af-muted); line-height:1.6; }
|
||||||
|
.agentflow-agent-dialog strong { color:var(--af-text); }
|
||||||
|
.agentflow-dialog-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:20px; }
|
||||||
|
body[theme="dark"] .agentflow-shell {
|
||||||
|
--af-panel:#202733;
|
||||||
|
--af-panel-soft:#171d27;
|
||||||
|
--af-border:#3b4658;
|
||||||
|
--af-text:#edf2fb;
|
||||||
|
--af-muted:#adb8cb;
|
||||||
|
--af-primary:#5d88ff;
|
||||||
|
--af-primary-hover:#7197ff;
|
||||||
|
--af-success:#50c878;
|
||||||
|
--af-success-soft:rgba(80, 200, 120, .14);
|
||||||
|
--af-shadow:0 8px 24px rgba(0, 0, 0, .18);
|
||||||
|
}
|
||||||
|
body[theme="dark"] .agentflow-section-mark { background:rgba(93, 136, 255, .14); }
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body:not([theme]) .agentflow-agent-dialog { border-color:#465166; background:#1f2937; color:#e5e7eb; }
|
body:not([theme]) .agentflow-shell {
|
||||||
body:not([theme]) .agentflow-agent-dialog > p { color:#aeb8c7; }
|
--af-panel:#202733;
|
||||||
|
--af-panel-soft:#171d27;
|
||||||
|
--af-border:#3b4658;
|
||||||
|
--af-text:#edf2fb;
|
||||||
|
--af-muted:#adb8cb;
|
||||||
|
--af-primary:#5d88ff;
|
||||||
|
--af-primary-hover:#7197ff;
|
||||||
|
--af-success:#50c878;
|
||||||
|
--af-success-soft:rgba(80, 200, 120, .14);
|
||||||
|
--af-shadow:0 8px 24px rgba(0, 0, 0, .18);
|
||||||
|
}
|
||||||
|
body:not([theme]) .agentflow-section-mark { background:rgba(93, 136, 255, .14); }
|
||||||
|
}
|
||||||
|
@media (max-width:1000px) {
|
||||||
|
.agentflow-agent-grid { grid-template-columns:repeat(2, minmax(0, 1fr)); }
|
||||||
|
}
|
||||||
|
@media (max-width:760px) {
|
||||||
|
.agentflow-overview { grid-template-columns:1fr; gap:20px; padding:18px; }
|
||||||
|
.agentflow-access { padding:18px 0 0; border-top:1px solid var(--af-border); border-left:0; }
|
||||||
|
.agentflow-agent-grid { grid-template-columns:1fr; }
|
||||||
|
.agentflow-agents { padding:18px 14px 14px; }
|
||||||
|
.agentflow-address-row { flex-wrap:wrap; }
|
||||||
|
.agentflow-address-row input[type="text"] { flex-basis:100%; }
|
||||||
|
.agentflow-address-row .agentflow-button { flex:1 1 auto; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">//<![CDATA[
|
<script type="text/javascript">//<![CDATA[
|
||||||
var agentflowOpenUrl = '<%=url("admin/services/agentflow/open")%>';
|
var agentflowOpenUrl = '<%=url("admin/services/agentflow/open")%>';
|
||||||
|
var agentflowStatusUrl = '<%=url("admin/services/agentflow_status")%>';
|
||||||
var agentflowAgentInstallUrl = '<%=url("admin/services/agentflow/agent_install")%>';
|
var agentflowAgentInstallUrl = '<%=url("admin/services/agentflow/agent_install")%>';
|
||||||
var agentflowCsrfToken = (window.taskd && window.taskd.csrfToken) || '';
|
var agentflowCsrfToken = (window.taskd && window.taskd.csrfToken) || '';
|
||||||
|
var agentflowSelectedAgent = '';
|
||||||
|
var agentflowAgentNames = {
|
||||||
|
codexcli: 'Codex',
|
||||||
|
'claude-code': 'Claude Code',
|
||||||
|
opencode: 'OpenCode',
|
||||||
|
kimi: 'Kimi',
|
||||||
|
reasonix: 'Reasonix'
|
||||||
|
};
|
||||||
|
var agentflowLabels = {
|
||||||
|
installed: '<%:Installed%>',
|
||||||
|
notInstalled: '<%:Not installed%>',
|
||||||
|
unknown: '<%:Status unavailable%>',
|
||||||
|
install: '<%:Install%>',
|
||||||
|
starting: '<%:Starting...%>',
|
||||||
|
copied: '<%:Copied%>'
|
||||||
|
};
|
||||||
|
|
||||||
function agentflowAgentDialog(open) {
|
function agentflowAbsoluteUrl(value) {
|
||||||
|
try { return new URL(value, window.location.href).href; }
|
||||||
|
catch (e) { return value || ''; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function agentflowRenderStatus(st) {
|
||||||
|
var icon = document.getElementById('agentflow-service-icon');
|
||||||
|
var title = document.getElementById('agentflow-service-title');
|
||||||
|
var description = document.getElementById('agentflow-service-description');
|
||||||
|
var openButton = document.getElementById('agentflow-open');
|
||||||
|
var address = document.getElementById('agentflow-address');
|
||||||
|
var statuses = {};
|
||||||
|
var agents = st && st.agents || [];
|
||||||
|
var i;
|
||||||
|
|
||||||
|
if (!st) return;
|
||||||
|
if (icon) {
|
||||||
|
icon.className = 'agentflow-service-icon' + (st.running ? ' running' : '');
|
||||||
|
icon.textContent = st.running ? '\u2713' : '!';
|
||||||
|
}
|
||||||
|
if (title) title.textContent = st.running ? '<%:AgentFlow service is running%>' : '<%:AgentFlow service is not running%>';
|
||||||
|
if (description) description.textContent = st.running
|
||||||
|
? '<%:The service is ready. You can open AgentFlow or install coding agents.%>'
|
||||||
|
: '<%:Start the service to use AgentFlow. Agent installation remains available.%>';
|
||||||
|
if (openButton) openButton.disabled = !st.running;
|
||||||
|
if (address) address.value = agentflowAbsoluteUrl(st.entry_url || '');
|
||||||
|
|
||||||
|
for (i = 0; i < agents.length; i++) statuses[agents[i].id] = agents[i];
|
||||||
|
for (i in agentflowAgentNames) {
|
||||||
|
if (!agentflowAgentNames.hasOwnProperty(i)) continue;
|
||||||
|
var card = document.getElementById('agentflow-agent-' + i);
|
||||||
|
var state = document.getElementById('agentflow-agent-state-' + i);
|
||||||
|
var version = document.getElementById('agentflow-agent-version-' + i);
|
||||||
|
var installButton = document.getElementById('agentflow-agent-install-' + i);
|
||||||
|
var item = statuses[i];
|
||||||
|
var available = st.agents_available !== false && !!item;
|
||||||
|
var installed = available && item.installed === true;
|
||||||
|
if (card) card.setAttribute('data-state', installed ? 'installed' : (available ? 'available' : 'unknown'));
|
||||||
|
if (state) state.textContent = installed ? agentflowLabels.installed : (available ? agentflowLabels.notInstalled : agentflowLabels.unknown);
|
||||||
|
if (version) version.textContent = installed && item.version ? item.version : '-';
|
||||||
|
if (installButton) {
|
||||||
|
installButton.disabled = !available || installed;
|
||||||
|
installButton.value = installed ? agentflowLabels.installed : agentflowLabels.install;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function agentflowCopyAddress() {
|
||||||
|
var address = document.getElementById('agentflow-address');
|
||||||
|
var feedback = document.getElementById('agentflow-copy-feedback');
|
||||||
|
if (!address || !address.value) return;
|
||||||
|
function done() {
|
||||||
|
if (!feedback) return;
|
||||||
|
feedback.textContent = agentflowLabels.copied;
|
||||||
|
window.setTimeout(function() { feedback.textContent = ''; }, 1800);
|
||||||
|
}
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard.writeText(address.value).then(done, function() {
|
||||||
|
address.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
address.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function agentflowAgentDialog(open, agent) {
|
||||||
var modal = document.getElementById('agentflow-agent-modal');
|
var modal = document.getElementById('agentflow-agent-modal');
|
||||||
var select = document.getElementById('agentflow-agent-select');
|
var name = document.getElementById('agentflow-selected-agent-name');
|
||||||
var trigger = document.getElementById('agentflow-agent-open');
|
var confirmButton = document.getElementById('agentflow-agent-install-now');
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
if (open) {
|
if (open) {
|
||||||
|
agentflowSelectedAgent = agent || '';
|
||||||
|
if (name) name.textContent = agentflowAgentNames[agentflowSelectedAgent] || agentflowSelectedAgent;
|
||||||
modal.classList.add('open');
|
modal.classList.add('open');
|
||||||
if (select) select.focus();
|
if (confirmButton) confirmButton.focus();
|
||||||
} else {
|
} else {
|
||||||
modal.classList.remove('open');
|
modal.classList.remove('open');
|
||||||
|
var trigger = document.getElementById('agentflow-agent-install-' + agentflowSelectedAgent);
|
||||||
if (trigger) trigger.focus();
|
if (trigger) trigger.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function agentflowInstallAgent() {
|
function agentflowInstallAgent() {
|
||||||
var selected = document.getElementById('agentflow-agent-select');
|
|
||||||
var button = document.getElementById('agentflow-agent-install-now');
|
var button = document.getElementById('agentflow-agent-install-now');
|
||||||
if (!selected || !button) return;
|
if (!agentflowSelectedAgent || !button) return;
|
||||||
if (!window.taskd || !window.taskd.show_log) {
|
if (!window.taskd || !window.taskd.show_log) {
|
||||||
alert('<%:Task log window is not available.%>');
|
alert('<%:Task log window is not available.%>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
|
button.value = agentflowLabels.starting;
|
||||||
(new XHR()).post(agentflowAgentInstallUrl, {
|
(new XHR()).post(agentflowAgentInstallUrl, {
|
||||||
agent: selected.value,
|
agent: agentflowSelectedAgent,
|
||||||
token: agentflowCsrfToken
|
token: agentflowCsrfToken
|
||||||
}, function(x, data) {
|
}, function(x, data) {
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
|
button.value = '<%:Install now%>';
|
||||||
if (!data) {
|
if (!data) {
|
||||||
try { data = JSON.parse(x.responseText || '{}'); }
|
try { data = JSON.parse(x.responseText || '{}'); }
|
||||||
catch (e) { data = {}; }
|
catch (e) { data = {}; }
|
||||||
@@ -66,25 +386,8 @@ function agentflowInstallAgent() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
XHR.poll(5, '<%=url("admin/services/agentflow_status")%>', null, function(x, st) {
|
XHR.poll(5, agentflowStatusUrl, null, function(x, st) {
|
||||||
var el = document.getElementById('agentflow_status');
|
agentflowRenderStatus(st);
|
||||||
var openButton = document.getElementById('agentflow-open');
|
|
||||||
if (st && el) {
|
|
||||||
if (!st.running) {
|
|
||||||
if (openButton) openButton.style.display = 'none';
|
|
||||||
el.innerHTML = '<br/><em style="color:red"><%:The AgentFlow service is not running.%></em>';
|
|
||||||
} else {
|
|
||||||
if (openButton) openButton.style.display = '';
|
|
||||||
var accessMode = '';
|
|
||||||
if (st.proxy_prefix_enabled && st.linkeasefull_running) {
|
|
||||||
accessMode = '<br/><em><%:Access mode: LinkEase Desktop /apps proxy.%></em>';
|
|
||||||
} else {
|
|
||||||
accessMode = '<br/><em><%:Access mode: external port.%></em>';
|
|
||||||
}
|
|
||||||
el.innerHTML = '<br/><em style="color:green"><%:The AgentFlow service is running.%></em>'
|
|
||||||
+ accessMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', function(ev) {
|
document.addEventListener('keydown', function(ev) {
|
||||||
@@ -92,34 +395,71 @@ document.addEventListener('keydown', function(ev) {
|
|||||||
});
|
});
|
||||||
//]]></script>
|
//]]></script>
|
||||||
|
|
||||||
<fieldset class="cbi-section">
|
<div class="agentflow-shell">
|
||||||
<legend><%:Status%></legend>
|
<section class="agentflow-panel agentflow-overview" aria-labelledby="agentflow-service-title">
|
||||||
<p id="agentflow_status">
|
<div class="agentflow-service">
|
||||||
<em><%:Collecting data...%></em>
|
<div id="agentflow-service-icon" class="agentflow-service-icon" aria-hidden="true">...</div>
|
||||||
</p>
|
<div class="agentflow-service-copy">
|
||||||
<div class="agentflow-actions">
|
<h3 id="agentflow-service-title"><%:Checking AgentFlow service%></h3>
|
||||||
<input id="agentflow-open" class="btn cbi-button cbi-button-apply" type="button" value="<%:Click to open AgentFlow%>" onclick="window.open(agentflowOpenUrl)" style="display:none">
|
<p id="agentflow-service-description"><%:Collecting data...%></p>
|
||||||
<input id="agentflow-agent-open" class="btn cbi-button cbi-button-apply" type="button" value="<%:Install agent%>" onclick="agentflowAgentDialog(true)">
|
<input id="agentflow-open" class="btn cbi-button agentflow-button agentflow-primary" type="button" value="<%:Open AgentFlow%>" onclick="window.open(agentflowOpenUrl)" disabled>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div id="agentflow-agent-modal" class="agentflow-agent-modal" role="dialog" aria-modal="true" aria-labelledby="agentflow-agent-title" onclick="if (event.target === this) agentflowAgentDialog(false)">
|
|
||||||
<div class="agentflow-agent-dialog">
|
|
||||||
<h3 id="agentflow-agent-title"><%:Install agent%></h3>
|
|
||||||
<p><%:Select a coding agent to install into the shared AgentFlow runtime.%></p>
|
|
||||||
<div class="agentflow-agent-field">
|
|
||||||
<label for="agentflow-agent-select"><%:Agent%></label>
|
|
||||||
<select id="agentflow-agent-select" class="cbi-input-select">
|
|
||||||
<option value="codexcli">Codex</option>
|
|
||||||
<option value="claude-code">Claude Code</option>
|
|
||||||
<option value="opencode">OpenCode</option>
|
|
||||||
<option value="kimi">Kimi</option>
|
|
||||||
<option value="reasonix">Reasonix</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="agentflow-agent-dialog-actions">
|
<div class="agentflow-access">
|
||||||
<input class="btn cbi-button" type="button" value="<%:Cancel%>" onclick="agentflowAgentDialog(false)">
|
<label for="agentflow-address"><%:Local network access%></label>
|
||||||
<input id="agentflow-agent-install-now" class="btn cbi-button cbi-button-apply" type="button" value="<%:Install now%>" onclick="agentflowInstallAgent()">
|
<div class="agentflow-address-row">
|
||||||
|
<input id="agentflow-address" type="text" readonly value="">
|
||||||
|
<input class="btn cbi-button agentflow-button" type="button" value="<%:Copy%>" onclick="agentflowCopyAddress()">
|
||||||
|
</div>
|
||||||
|
<p class="agentflow-access-note"><%:Open this address in a browser to access the AgentFlow Web interface.%> <span id="agentflow-copy-feedback" role="status" aria-live="polite"></span></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="agentflow-panel agentflow-agents" aria-labelledby="agentflow-agents-title">
|
||||||
|
<div class="agentflow-section-heading">
|
||||||
|
<div class="agentflow-section-mark" aria-hidden="true">A</div>
|
||||||
|
<div>
|
||||||
|
<h3 id="agentflow-agents-title"><%:Agent installation%></h3>
|
||||||
|
<p><%:Install coding agents into the shared AgentFlow runtime. Installed agents show their current version.%></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="agentflow-agent-grid">
|
||||||
|
<div id="agentflow-agent-codexcli" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#356cf6">
|
||||||
|
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">C</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">Codex</div><p><%:OpenAI coding agent for software development tasks.%></p></div></div>
|
||||||
|
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-codexcli"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-codexcli">-</span></span></div>
|
||||||
|
<input id="agentflow-agent-install-codexcli" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'codexcli')" disabled>
|
||||||
|
</div>
|
||||||
|
<div id="agentflow-agent-claude-code" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#dc633d">
|
||||||
|
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">C</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">Claude Code</div><p><%:Anthropic coding agent for code analysis and development.%></p></div></div>
|
||||||
|
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-claude-code"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-claude-code">-</span></span></div>
|
||||||
|
<input id="agentflow-agent-install-claude-code" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'claude-code')" disabled>
|
||||||
|
</div>
|
||||||
|
<div id="agentflow-agent-opencode" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#6a3ee8">
|
||||||
|
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">O</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">OpenCode</div><p><%:Open source coding agent with a flexible terminal workflow.%></p></div></div>
|
||||||
|
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-opencode"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-opencode">-</span></span></div>
|
||||||
|
<input id="agentflow-agent-install-opencode" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'opencode')" disabled>
|
||||||
|
</div>
|
||||||
|
<div id="agentflow-agent-kimi" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#1677d2">
|
||||||
|
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">K</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">Kimi</div><p><%:Moonshot AI coding agent for terminal-based development.%></p></div></div>
|
||||||
|
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-kimi"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-kimi">-</span></span></div>
|
||||||
|
<input id="agentflow-agent-install-kimi" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'kimi')" disabled>
|
||||||
|
</div>
|
||||||
|
<div id="agentflow-agent-reasonix" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#3f526f">
|
||||||
|
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">R</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">Reasonix</div><p><%:AI coding agent for software engineering workflows.%></p></div></div>
|
||||||
|
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-reasonix"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-reasonix">-</span></span></div>
|
||||||
|
<input id="agentflow-agent-install-reasonix" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'reasonix')" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div id="agentflow-agent-modal" class="agentflow-agent-modal" role="dialog" aria-modal="true" aria-labelledby="agentflow-agent-dialog-title" onclick="if (event.target === this) agentflowAgentDialog(false)">
|
||||||
|
<div class="agentflow-agent-dialog">
|
||||||
|
<h3 id="agentflow-agent-dialog-title"><%:Confirm agent installation%></h3>
|
||||||
|
<p><%:The installer will download and install%> <strong id="agentflow-selected-agent-name"></strong> <%:into the shared AgentFlow runtime.%></p>
|
||||||
|
<div class="agentflow-dialog-actions">
|
||||||
|
<input class="btn cbi-button agentflow-button" type="button" value="<%:Cancel%>" onclick="agentflowAgentDialog(false)">
|
||||||
|
<input id="agentflow-agent-install-now" class="btn cbi-button agentflow-button agentflow-primary" type="button" value="<%:Install now%>" onclick="agentflowInstallAgent()">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,3 +72,84 @@ msgstr "任务日志窗口不可用。"
|
|||||||
|
|
||||||
msgid "Failed to start agent installation."
|
msgid "Failed to start agent installation."
|
||||||
msgstr "启动智能体安装失败。"
|
msgstr "启动智能体安装失败。"
|
||||||
|
|
||||||
|
msgid "Installed"
|
||||||
|
msgstr "已安装"
|
||||||
|
|
||||||
|
msgid "Not installed"
|
||||||
|
msgstr "未安装"
|
||||||
|
|
||||||
|
msgid "Status unavailable"
|
||||||
|
msgstr "状态不可用"
|
||||||
|
|
||||||
|
msgid "Install"
|
||||||
|
msgstr "安装"
|
||||||
|
|
||||||
|
msgid "Starting..."
|
||||||
|
msgstr "正在启动……"
|
||||||
|
|
||||||
|
msgid "Copied"
|
||||||
|
msgstr "已复制"
|
||||||
|
|
||||||
|
msgid "Copy"
|
||||||
|
msgstr "复制"
|
||||||
|
|
||||||
|
msgid "AgentFlow service is running"
|
||||||
|
msgstr "AgentFlow 服务运行中"
|
||||||
|
|
||||||
|
msgid "AgentFlow service is not running"
|
||||||
|
msgstr "AgentFlow 服务未运行"
|
||||||
|
|
||||||
|
msgid "The service is ready. You can open AgentFlow or install coding agents."
|
||||||
|
msgstr "服务已就绪,可以打开 AgentFlow 或安装编码智能体。"
|
||||||
|
|
||||||
|
msgid "Start the service to use AgentFlow. Agent installation remains available."
|
||||||
|
msgstr "请启动服务后使用 AgentFlow;智能体安装仍然可用。"
|
||||||
|
|
||||||
|
msgid "Checking AgentFlow service"
|
||||||
|
msgstr "正在检查 AgentFlow 服务"
|
||||||
|
|
||||||
|
msgid "Open AgentFlow"
|
||||||
|
msgstr "打开 AgentFlow"
|
||||||
|
|
||||||
|
msgid "Local network access"
|
||||||
|
msgstr "局域网访问"
|
||||||
|
|
||||||
|
msgid "Open this address in a browser to access the AgentFlow Web interface."
|
||||||
|
msgstr "在浏览器中打开此地址即可访问 AgentFlow Web 界面。"
|
||||||
|
|
||||||
|
msgid "Agent installation"
|
||||||
|
msgstr "智能体安装"
|
||||||
|
|
||||||
|
msgid "Install coding agents into the shared AgentFlow runtime. Installed agents show their current version."
|
||||||
|
msgstr "将编码智能体安装到 AgentFlow 共享运行环境中;已安装的智能体会显示当前版本。"
|
||||||
|
|
||||||
|
msgid "OpenAI coding agent for software development tasks."
|
||||||
|
msgstr "OpenAI 提供的代码智能体,适用于软件开发任务。"
|
||||||
|
|
||||||
|
msgid "Anthropic coding agent for code analysis and development."
|
||||||
|
msgstr "Anthropic 提供的代码智能体,适用于代码分析与开发。"
|
||||||
|
|
||||||
|
msgid "Open source coding agent with a flexible terminal workflow."
|
||||||
|
msgstr "开源代码智能体,提供灵活的终端开发工作流。"
|
||||||
|
|
||||||
|
msgid "Moonshot AI coding agent for terminal-based development."
|
||||||
|
msgstr "Moonshot AI 提供的代码智能体,适用于终端开发。"
|
||||||
|
|
||||||
|
msgid "AI coding agent for software engineering workflows."
|
||||||
|
msgstr "面向软件工程工作流的 AI 代码智能体。"
|
||||||
|
|
||||||
|
msgid "Checking..."
|
||||||
|
msgstr "正在检查……"
|
||||||
|
|
||||||
|
msgid "Version"
|
||||||
|
msgstr "版本"
|
||||||
|
|
||||||
|
msgid "Confirm agent installation"
|
||||||
|
msgstr "确认安装智能体"
|
||||||
|
|
||||||
|
msgid "The installer will download and install"
|
||||||
|
msgstr "安装程序将下载并安装"
|
||||||
|
|
||||||
|
msgid "into the shared AgentFlow runtime."
|
||||||
|
msgstr "到 AgentFlow 共享运行环境。"
|
||||||
@@ -16,37 +16,57 @@ class AgentInstallContractTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertIn("+luci-lib-taskd", makefile)
|
self.assertIn("+luci-lib-taskd", makefile)
|
||||||
self.assertIn('<%+tasks/embed%>', status)
|
self.assertIn('<%+tasks/embed%>', status)
|
||||||
self.assertIn('<select id="agentflow-agent-select"', status)
|
|
||||||
for agent in ("codexcli", "claude-code", "opencode", "kimi", "reasonix"):
|
for agent in ("codexcli", "claude-code", "opencode", "kimi", "reasonix"):
|
||||||
self.assertIn(f'<option value="{agent}">', status)
|
self.assertIn(f'id="agentflow-agent-{agent}"', status)
|
||||||
self.assertNotIn('type="radio"', status)
|
self.assertIn(f"agentflowAgentDialog(true, '{agent}')", status)
|
||||||
|
self.assertNotIn('<select id="agentflow-agent-select"', status)
|
||||||
|
self.assertNotIn("<h4>", status)
|
||||||
|
self.assertEqual(status.count('class="agentflow-agent-title"'), 5)
|
||||||
|
self.assertNotIn("Update", status)
|
||||||
|
self.assertNotIn("Uninstall", status)
|
||||||
self.assertIn('id="agentflow-open"', status)
|
self.assertIn('id="agentflow-open"', status)
|
||||||
self.assertIn('id="agentflow-agent-open"', status)
|
self.assertIn('id="agentflow-agent-install-now"', status)
|
||||||
self.assertGreater(status.index('id="agentflow-agent-open"'), status.index('id="agentflow_status"'))
|
self.assertIn("agentflowSelectedAgent", status)
|
||||||
self.assertIn('margin:12px;', status)
|
|
||||||
self.assertIn("var selected = document.getElementById('agentflow-agent-select')", status)
|
|
||||||
self.assertIn("window.taskd.show_log(data.task_id", status)
|
self.assertIn("window.taskd.show_log(data.task_id", status)
|
||||||
self.assertNotIn('local token =', status)
|
self.assertNotIn('local token =', status)
|
||||||
self.assertIn("window.taskd.csrfToken", status)
|
self.assertIn("window.taskd.csrfToken", status)
|
||||||
self.assertIn("token: agentflowCsrfToken", status)
|
self.assertIn("token: agentflowCsrfToken", status)
|
||||||
self.assertIn('http.formvalue("agent")', controller)
|
self.assertIn('http.formvalue("agent")', controller)
|
||||||
self.assertIn('codexcli = true', controller)
|
for agent in ("codexcli", "claude-code", "opencode", "kimi", "reasonix"):
|
||||||
self.assertIn('["claude-code"] = true', controller)
|
self.assertIn(f'id = "{agent}"', controller)
|
||||||
self.assertIn('opencode = true', controller)
|
self.assertIn('supported_agents[supported_agent.id] = true', controller)
|
||||||
self.assertIn('kimi = true', controller)
|
|
||||||
self.assertIn('reasonix = true', controller)
|
|
||||||
self.assertIn('context.authtoken or context.token', controller)
|
self.assertIn('context.authtoken or context.token', controller)
|
||||||
self.assertIn('http.formvalue("token") ~= expected', controller)
|
self.assertIn('http.formvalue("token") ~= expected', controller)
|
||||||
self.assertIn('/etc/init.d/tasks task_add ', controller)
|
self.assertIn('/etc/init.d/tasks task_add ', controller)
|
||||||
self.assertIn('task_id = "agentflow-agent-install"', controller)
|
self.assertIn('task_id = "agentflow-agent-install"', controller)
|
||||||
|
|
||||||
|
def test_status_reports_installed_agents_from_shared_mise_runtime(self):
|
||||||
|
controller = self.read("luasrc/controller/agentflow.lua")
|
||||||
|
status = self.read("luasrc/view/agentflow/status.htm")
|
||||||
|
|
||||||
|
self.assertIn("istore_runtime_env", controller)
|
||||||
|
self.assertIn('where node@lts', controller)
|
||||||
|
for package in (
|
||||||
|
"@openai/codex",
|
||||||
|
"@anthropic-ai/claude-code",
|
||||||
|
"opencode-ai",
|
||||||
|
"@moonshot-ai/kimi-code",
|
||||||
|
"reasonix",
|
||||||
|
):
|
||||||
|
self.assertIn(f'package = "{package}"', controller)
|
||||||
|
self.assertIn('status.version = package_json.version', controller)
|
||||||
|
self.assertIn('agents_available = agents_available', controller)
|
||||||
|
self.assertIn('st.agents_available', status)
|
||||||
|
self.assertIn('item.installed === true', status)
|
||||||
|
self.assertIn('installButton.disabled = !available || installed', status)
|
||||||
|
self.assertIn('installed ? agentflowLabels.installed : agentflowLabels.install', status)
|
||||||
|
|
||||||
def test_modal_theme_prefers_body_attribute_then_system_theme(self):
|
def test_modal_theme_prefers_body_attribute_then_system_theme(self):
|
||||||
status = self.read("luasrc/view/agentflow/status.htm")
|
status = self.read("luasrc/view/agentflow/status.htm")
|
||||||
|
|
||||||
self.assertIn('body[theme="light"] .agentflow-agent-dialog', status)
|
self.assertIn('body[theme="dark"] .agentflow-shell', status)
|
||||||
self.assertIn('body[theme="dark"] .agentflow-agent-dialog', status)
|
|
||||||
self.assertIn('@media (prefers-color-scheme: dark)', status)
|
self.assertIn('@media (prefers-color-scheme: dark)', status)
|
||||||
self.assertIn('body:not([theme]) .agentflow-agent-dialog', status)
|
self.assertIn('body:not([theme]) .agentflow-shell', status)
|
||||||
|
|
||||||
def test_controller_runs_remote_installer_inside_taskd(self):
|
def test_controller_runs_remote_installer_inside_taskd(self):
|
||||||
controller = self.read("luasrc/controller/agentflow.lua")
|
controller = self.read("luasrc/controller/agentflow.lua")
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ LUCI_DESCRIPTION:=Live firewall log table (firewall4/nft; iptables-tagged lines
|
|||||||
LUCI_DEPENDS:=+luci-base +logd +jsonfilter
|
LUCI_DEPENDS:=+luci-base +logd +jsonfilter
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
PKG_VERSION:=0.1.44
|
PKG_VERSION:=0.1.45
|
||||||
PKG_RELEASE:=28
|
PKG_RELEASE:=29
|
||||||
|
|
||||||
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
||||||
ifdef SOURCE_DATE_EPOCH
|
ifdef SOURCE_DATE_EPOCH
|
||||||
@@ -28,11 +28,34 @@ case "$${IPKG_INSTROOT}" in
|
|||||||
''|/) ;;
|
''|/) ;;
|
||||||
*) exit 0 ;;
|
*) exit 0 ;;
|
||||||
esac
|
esac
|
||||||
|
# The opkg wrapper invokes this body as <wrapper> <action>.
|
||||||
|
# OpenWrt APK post-upgrade/pre-deinstall wiring is in package-pack.mk;
|
||||||
|
# post-upgrade exports PKG_UPGRADE=1. This guard is belt-and-braces with
|
||||||
|
# the upgrade action arg.
|
||||||
|
# A version-shaped $$1 with empty $$2 is APK uninstall (pre-deinstall).
|
||||||
|
# Same-version APK reinstall uses post-upgrade and does not run this hook.
|
||||||
|
# A version-changing APK upgrade is indistinguishable from uninstall at
|
||||||
|
# this hook (both pass the old version as $$1). Do not claim that path
|
||||||
|
# was lab-proven.
|
||||||
|
[ "$${PKG_UPGRADE:-}" = 1 ] && exit 0
|
||||||
|
case "$${2-}" in
|
||||||
|
remove) ;;
|
||||||
|
upgrade) exit 0 ;;
|
||||||
|
'')
|
||||||
|
case "$${1-}" in
|
||||||
|
remove) ;;
|
||||||
|
upgrade|''|[!0-9]*) exit 0 ;;
|
||||||
|
# Character-class '.' so the version arm is a literal dot (1a2 must not restore).
|
||||||
|
[0-9]*[.][0-9]*) ;;
|
||||||
|
*) exit 0 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*) exit 0 ;;
|
||||||
|
esac
|
||||||
. /usr/libexec/fwlive-logging.sh
|
. /usr/libexec/fwlive-logging.sh
|
||||||
# Best-effort restore: always exit 0 so a transient lock/pending-staging
|
# Best-effort restore still exits 0 so a transient lock/pending-staging failure
|
||||||
# failure cannot strand the package half-removed. Baseline is kept on
|
# cannot strand the package half-removed. The baseline is kept on failure for
|
||||||
# failure (restore_wan_log_baseline) for admin recovery; helpers are still
|
# admin recovery; helpers are still present while this prerm runs.
|
||||||
# present while this prerm runs.
|
|
||||||
restore_wan_log_baseline || logger -t fwlive "WAN log baseline restore failed during uninstall"
|
restore_wan_log_baseline || logger -t fwlive "WAN log baseline restore failed during uninstall"
|
||||||
exit 0
|
exit 0
|
||||||
endef
|
endef
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
return baseclass.extend({
|
return baseclass.extend({
|
||||||
/* Keep in sync with openwrt-feed/luci-app-fwlive/Makefile PKG_VERSION. */
|
/* Keep in sync with openwrt-feed/luci-app-fwlive/Makefile PKG_VERSION. */
|
||||||
APP_VERSION: '0.1.44',
|
APP_VERSION: '0.1.45',
|
||||||
ROW_LIMIT_OPTIONS: [25, 50, 100, 250, 500, 1000, 2000],
|
ROW_LIMIT_OPTIONS: [25, 50, 100, 250, 500, 1000, 2000],
|
||||||
DEFAULT_ROW_LIMIT: 100,
|
DEFAULT_ROW_LIMIT: 100,
|
||||||
FETCH_MODE_OPTIONS: ['auto', 'manual'],
|
FETCH_MODE_OPTIONS: ['auto', 'manual'],
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ return view.extend({
|
|||||||
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_user', title: _('PPPoE Username'), depends: { wan_proto: 'pppoe' } },
|
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_user', title: _('PPPoE Username'), depends: { wan_proto: 'pppoe' } },
|
||||||
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_pass', title: _('PPPoE Password'), depends: { wan_proto: 'pppoe' }, password: true },
|
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_pass', title: _('PPPoE Password'), depends: { wan_proto: 'pppoe' }, password: true },
|
||||||
{ tab: 'netsetup', type: form.Value, id: 'lan_ipaddr', title: _('LAN IPv4 Address'), datatype: 'ip4addr', placeholder: '10.0.0.1' },
|
{ tab: 'netsetup', type: form.Value, id: 'lan_ipaddr', title: _('LAN IPv4 Address'), datatype: 'ip4addr', placeholder: '10.0.0.1' },
|
||||||
{ tab: 'netsetup', type: form.DynamicList, id: 'lan_dns', title: _('Custom DNS Server(s)'), datatype: 'ipaddr', placeholder: '223.5.5.5' },
|
{ tab: 'netsetup', type: form.DynamicList, id: 'lan_dns', title: _('Custom DNS Server(s)'), datatype: 'ipaddr', placeholder: '223.5.5.5', desc: _('Leave empty for ISP DNS.') },
|
||||||
{ tab: 'netsetup', type: form.Value, id: 'lan_gateway', title: _('Gateway Address'), datatype: 'ip4addr', placeholder: '', depends: { wan_proto: 'siderouter' }, desc: _('Primary router IP address used when operating in side-router mode.') },
|
{ tab: 'netsetup', type: form.Value, id: 'lan_gateway', title: _('Gateway Address'), datatype: 'ip4addr', placeholder: '', depends: { wan_proto: 'siderouter' }, desc: _('Primary router IP address used when operating in side-router mode.') },
|
||||||
{ tab: 'netsetup', type: form.Flag, id: 'dhcp', title: _('Enable DHCP Server'), desc: _('Enable or disable LAN DHCP server (usually disabled in side-router mode to avoid IP address conflicts).') },
|
{ tab: 'netsetup', type: form.Flag, id: 'dhcp', title: _('Enable DHCP Server'), desc: _('Enable or disable LAN DHCP server (usually disabled in side-router mode to avoid IP address conflicts).') },
|
||||||
{ tab: 'netsetup', type: form.Flag, id: 'ipv6', title: _('IPv6 Support'), desc: _('Enable or disable IPv6 router advertisements and DHCPv6.') },
|
{ tab: 'netsetup', type: form.Flag, id: 'ipv6', title: _('IPv6 Support'), desc: _('Enable or disable IPv6 router advertisements and DHCPv6.') },
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ msgstr "启用 DHCP 服务器"
|
|||||||
msgid "Enable or disable LAN DHCP server (usually disabled in side-router mode to avoid IP address conflicts)."
|
msgid "Enable or disable LAN DHCP server (usually disabled in side-router mode to avoid IP address conflicts)."
|
||||||
msgstr "开启或关闭局域网 DHCP 服务(旁路由模式下通常建议关闭,避免局域网 IP 分配冲突)。"
|
msgstr "开启或关闭局域网 DHCP 服务(旁路由模式下通常建议关闭,避免局域网 IP 分配冲突)。"
|
||||||
|
|
||||||
|
msgid "Leave empty for ISP DNS."
|
||||||
|
msgstr "留空则使用 ISP 运营商DNS"
|
||||||
|
|
||||||
msgid "IPv6 Support"
|
msgid "IPv6 Support"
|
||||||
msgstr "IPv6 支持"
|
msgstr "IPv6 支持"
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-wwand
|
PKG_NAME:=luci-app-wwand
|
||||||
PKG_VERSION:=1.6.7_p11
|
PKG_VERSION:=1.6.7_p13
|
||||||
PKG_RELEASE:=24
|
PKG_RELEASE:=25
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
|
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
|
||||||
PKG_SOURCE_VERSION:=2ca9a94506f972d89a398d560d5667a809e89d9f
|
PKG_SOURCE_VERSION:=e97ffb8db7ca59049195d9a37ae86b69c1e1264d
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-2.0-only
|
PKG_LICENSE:=GPL-2.0-only
|
||||||
|
|||||||
@@ -205,6 +205,13 @@ return view.extend({
|
|||||||
s.tab('general', _('General Settings'));
|
s.tab('general', _('General Settings'));
|
||||||
s.tab('advanced', _('Advanced Settings'));
|
s.tab('advanced', _('Advanced Settings'));
|
||||||
|
|
||||||
|
o = s.taboption('general', form.Flag, 'enabled', _('Enable Client Daemon'),
|
||||||
|
_('Enable unified xkcptun client daemon.'));
|
||||||
|
o.enabled = '1';
|
||||||
|
o.disabled = '0';
|
||||||
|
o.default = '0';
|
||||||
|
o.rmempty = false;
|
||||||
|
|
||||||
o = s.taboption('general', form.Value, 'remote_addr', _('Default Server Address'),
|
o = s.taboption('general', form.Value, 'remote_addr', _('Default Server Address'),
|
||||||
_('Remote xkcptun server IP address or domain name.'));
|
_('Remote xkcptun server IP address or domain name.'));
|
||||||
o.datatype = 'host';
|
o.datatype = 'host';
|
||||||
@@ -244,10 +251,10 @@ return view.extend({
|
|||||||
s.tab('advanced', _('Advanced Overrides'));
|
s.tab('advanced', _('Advanced Overrides'));
|
||||||
|
|
||||||
// Overview Columns in table
|
// Overview Columns in table
|
||||||
o = s.option(form.Flag, 'disabled', _('Enabled'));
|
o = s.option(form.Flag, 'enabled', _('Enabled'));
|
||||||
o.enabled = '0';
|
o.enabled = '1';
|
||||||
o.disabled = '1';
|
o.disabled = '0';
|
||||||
o.default = '0';
|
o.default = '1';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.editable = true;
|
o.editable = true;
|
||||||
|
|
||||||
@@ -274,10 +281,10 @@ return view.extend({
|
|||||||
o.placeholder = '127.0.0.1';
|
o.placeholder = '127.0.0.1';
|
||||||
|
|
||||||
// Modal / Tab: General Settings
|
// Modal / Tab: General Settings
|
||||||
o = s.taboption('general', form.Flag, 'disabled', _('Enable Tunnel'));
|
o = s.taboption('general', form.Flag, 'enabled', _('Enable Tunnel'));
|
||||||
o.enabled = '0';
|
o.enabled = '1';
|
||||||
o.disabled = '1';
|
o.disabled = '0';
|
||||||
o.default = '0';
|
o.default = '1';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
@@ -399,10 +406,10 @@ return view.extend({
|
|||||||
s.tab('advanced', _('Advanced Settings'));
|
s.tab('advanced', _('Advanced Settings'));
|
||||||
|
|
||||||
// Overview Columns in table
|
// Overview Columns in table
|
||||||
o = s.option(form.Flag, 'disabled', _('Enabled'));
|
o = s.option(form.Flag, 'enabled', _('Enabled'));
|
||||||
o.enabled = '0';
|
o.enabled = '1';
|
||||||
o.disabled = '1';
|
o.disabled = '0';
|
||||||
o.default = '0';
|
o.default = '1';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.editable = true;
|
o.editable = true;
|
||||||
|
|
||||||
@@ -434,14 +441,14 @@ return view.extend({
|
|||||||
|
|
||||||
o = s.option(form.Flag, 'fec', _('FEC'));
|
o = s.option(form.Flag, 'fec', _('FEC'));
|
||||||
o.modalonly = false;
|
o.modalonly = false;
|
||||||
o.default = '0';
|
o.default = '1';
|
||||||
o.editable = true;
|
o.editable = true;
|
||||||
|
|
||||||
// Modal / Tab: General Settings
|
// Modal / Tab: General Settings
|
||||||
o = s.taboption('general', form.Flag, 'disabled', _('Enable Tunnel'));
|
o = s.taboption('general', form.Flag, 'enabled', _('Enable Tunnel'));
|
||||||
o.enabled = '0';
|
o.enabled = '1';
|
||||||
o.disabled = '1';
|
o.disabled = '0';
|
||||||
o.default = '0';
|
o.default = '1';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ include $(INCLUDE_DIR)/kernel.mk
|
|||||||
|
|
||||||
PKG_NAME:=my-default-settings
|
PKG_NAME:=my-default-settings
|
||||||
PKG_VERSION:=2
|
PKG_VERSION:=2
|
||||||
PKG_RELEASE:=16
|
PKG_RELEASE:=17
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
|||||||
@@ -16,25 +16,33 @@ show_help() {
|
|||||||
选项:
|
选项:
|
||||||
-s, --ssid <名称> 指定基础WiFi名称
|
-s, --ssid <名称> 指定基础WiFi名称
|
||||||
-p, --password <密码> 指定WiFi密码
|
-p, --password <密码> 指定WiFi密码
|
||||||
|
-f, --force 强制重新执行(忽略 wifi=1 已配置检查)
|
||||||
-h, --help 查看帮助信息
|
-h, --help 查看帮助信息
|
||||||
|
|
||||||
|
并发与等待机制:
|
||||||
|
- 无参调用时:若已有 mywifi 在执行,直接终止退出;
|
||||||
|
- 带 -f 或传参调用时:若已有 mywifi 在执行,自动排队等待其执行完成后再继续执行。
|
||||||
|
|
||||||
WiFi命名规范:
|
WiFi命名规范:
|
||||||
2.4 GHz -> <WiFi名>_2.4G
|
2.4 GHz -> <WiFi名>_2.4G
|
||||||
5 GHz -> <WiFi名>_5G
|
5 GHz -> <WiFi名>_5G
|
||||||
6 GHz -> <WiFi名>_6G
|
6 GHz -> <WiFi名>_6G
|
||||||
|
|
||||||
使用示例:
|
使用示例:
|
||||||
mywifi # 默认使用 hostname,无密码
|
mywifi # 默认执行(若已配置过或正在执行则自动终止)
|
||||||
mywifi "HomeWiFi" # HomeWiFi_2.4G / HomeWiFi_5G,无密码
|
mywifi -f # 强制重新按 hostname 恢复默认配置(若有执行中则排队等待)
|
||||||
mywifi "HomeWiFi" "12345678" # HomeWiFi_2.4G / HomeWiFi_5G,密码 12345678
|
mywifi "HomeWiFi" "12345678" # 设置指定 WiFi 名及密码(若有执行中则排队等待)
|
||||||
mywifi -s "HomeWiFi" -p "12345678" # 选项传参
|
mywifi -f "HomeWiFi" "12345678" # 强制覆盖设置
|
||||||
EOF_HELP
|
EOF_HELP
|
||||||
}
|
}
|
||||||
|
|
||||||
# 1. 解析命令行参数(同时兼顾位置参数与选项参数)
|
# -------------------------------------------------------------
|
||||||
|
# 1. 优先解析命令行参数(判断是否带有 -f 或自定义参数)
|
||||||
|
# -------------------------------------------------------------
|
||||||
CUSTOM_SSID=""
|
CUSTOM_SSID=""
|
||||||
CUSTOM_PASSWD=""
|
CUSTOM_PASSWD=""
|
||||||
HAS_PASSWD=0
|
HAS_PASSWD=0
|
||||||
|
FORCE=0
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -42,6 +50,9 @@ while [ $# -gt 0 ]; do
|
|||||||
show_help
|
show_help
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
|
-f|--force)
|
||||||
|
FORCE=1
|
||||||
|
;;
|
||||||
-s|--ssid)
|
-s|--ssid)
|
||||||
shift
|
shift
|
||||||
CUSTOM_SSID="$1"
|
CUSTOM_SSID="$1"
|
||||||
@@ -63,7 +74,94 @@ while [ $# -gt 0 ]; do
|
|||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
# 密码合法性校验
|
# 判断是否需要等待正在运行的实例完成
|
||||||
|
# 只要指定了 -f 或传入了自定义 SSID / 密码,就启用等待锁机制
|
||||||
|
WAIT_LOCK=0
|
||||||
|
if [ $FORCE -eq 1 ] || [ -n "$CUSTOM_SSID" ] || [ $HAS_PASSWD -eq 1 ]; then
|
||||||
|
WAIT_LOCK=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# 2. 检查 base_config 状态标记(无参且已配置过 wifi=1 时直接退出)
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
if [ $FORCE -eq 0 ]; then
|
||||||
|
if [ "$(uci -q get base_config.@status[0].wifi 2>/dev/null)" = "1" ]; then
|
||||||
|
if [ -z "$CUSTOM_SSID" ] && [ $HAS_PASSWD -eq 0 ]; then
|
||||||
|
echo "[INFO] WiFi 已配置完成 (base_config.@status[0].wifi=1),无需重复执行,直接终止脚本。"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# 3. 并发锁处理:带参/-f 模式等待完成,无参模式直接终止
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
LOCK_FILE="/var/run/mywifi.lock"
|
||||||
|
|
||||||
|
acquire_lock() {
|
||||||
|
local cur_pid=$$
|
||||||
|
local cur_ppid=$PPID
|
||||||
|
|
||||||
|
# A. 优先采用 flock 文件描述符锁(内核自动回收,安全可靠)
|
||||||
|
if command -v flock >/dev/null 2>&1; then
|
||||||
|
exec 9>"$LOCK_FILE" 2>/dev/null || true
|
||||||
|
if [ $WAIT_LOCK -eq 1 ]; then
|
||||||
|
if ! flock -n 9 2>/dev/null; then
|
||||||
|
echo "[INFO] 检测到 /sbin/mywifi 已在执行中,等待其执行完成后再继续..."
|
||||||
|
flock -w 60 9 2>/dev/null || {
|
||||||
|
echo "[ERROR] 等待其他 /sbin/mywifi 执行超时(60秒)!" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! flock -n 9 2>/dev/null; then
|
||||||
|
echo "[INFO] 检测到 /sbin/mywifi 已在执行中,直接终止脚本。"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# B. 备用进程轮询检测机制(兼容无 flock 极简环境)
|
||||||
|
local wait_limit=60
|
||||||
|
local waited=0
|
||||||
|
while true; do
|
||||||
|
local other_running=0
|
||||||
|
for p in $(pgrep -f "mywifi" 2>/dev/null); do
|
||||||
|
if [ "$p" != "$cur_pid" ] && [ "$p" != "$cur_ppid" ]; then
|
||||||
|
local cmd="$(cat /proc/$p/cmdline 2>/dev/null | tr '\0' ' ')"
|
||||||
|
case "$cmd" in
|
||||||
|
*mywifi*)
|
||||||
|
other_running=1
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $other_running -eq 0 ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $WAIT_LOCK -eq 1 ]; then
|
||||||
|
[ $waited -eq 0 ] && echo "[INFO] 检测到 /sbin/mywifi 已在执行中,等待其执行完成后再继续..."
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
if [ $waited -ge $wait_limit ]; then
|
||||||
|
echo "[ERROR] 等待其他 /sbin/mywifi 执行超时!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[INFO] 检测到 /sbin/mywifi 已在执行中,直接终止脚本。"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire_lock
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# 4. 密码长度合法性校验(若指定了密码)
|
||||||
|
# -------------------------------------------------------------
|
||||||
if [ $HAS_PASSWD -eq 1 ] && [ -n "$CUSTOM_PASSWD" ]; then
|
if [ $HAS_PASSWD -eq 1 ] && [ -n "$CUSTOM_PASSWD" ]; then
|
||||||
pwd_len=${#CUSTOM_PASSWD}
|
pwd_len=${#CUSTOM_PASSWD}
|
||||||
if [ "$pwd_len" -lt 8 ] || [ "$pwd_len" -gt 63 ]; then
|
if [ "$pwd_len" -lt 8 ] || [ "$pwd_len" -gt 63 ]; then
|
||||||
@@ -75,7 +173,9 @@ else
|
|||||||
SSID_PASSWD=""
|
SSID_PASSWD=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 2. 深度就绪检测:确保内核PHY驱动、多频硬件、系统初始化状态和UCI配置全部真正就绪
|
# -------------------------------------------------------------
|
||||||
|
# 5. 深度就绪检测(确保多频硬件驱动加载与 UCI 配置完整就绪)
|
||||||
|
# -------------------------------------------------------------
|
||||||
wait_wifi_ready() {
|
wait_wifi_ready() {
|
||||||
local max_wait=15
|
local max_wait=15
|
||||||
local waited=0
|
local waited=0
|
||||||
@@ -84,34 +184,30 @@ wait_wifi_ready() {
|
|||||||
local phy_count=0
|
local phy_count=0
|
||||||
local uci_count=0
|
local uci_count=0
|
||||||
|
|
||||||
# B. 检查内核物理无线设备 (PHY)
|
# 检查内核物理无线设备 (PHY)
|
||||||
local phys="$(ls -d /sys/class/ieee80211/phy* 2>/dev/null)"
|
local phys="$(ls -d /sys/class/ieee80211/phy* 2>/dev/null)"
|
||||||
[ -n "$phys" ] && phy_count=$(echo "$phys" | wc -w)
|
[ -n "$phys" ] && phy_count=$(echo "$phys" | wc -w)
|
||||||
|
|
||||||
# C. 统计 UCI wireless 中实际已解析出的 wifi-device 节点数量(非文件大小)
|
# 检查 UCI 中真实生成的 wifi-device 数量
|
||||||
if [ -s /etc/config/wireless ]; then
|
if [ -s /etc/config/wireless ]; then
|
||||||
local uci_radios="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
local uci_radios="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
||||||
[ -n "$uci_radios" ] && uci_count=$(echo "$uci_radios" | wc -w)
|
[ -n "$uci_radios" ] && uci_count=$(echo "$uci_radios" | wc -w)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# D. 自动探测与配置补全:
|
# 自动补全配置(物理硬件数量 > UCI 配置数量)
|
||||||
# - 物理网卡已加载,但 UCI 配置中的 radio 数量少于物理网卡数量(多频网卡尚未完全生成配置)
|
|
||||||
# - 或者配置文件缺失/没有 radio 配置
|
|
||||||
if [ $phy_count -gt 0 ] && [ $uci_count -lt $phy_count ]; then
|
if [ $phy_count -gt 0 ] && [ $uci_count -lt $phy_count ]; then
|
||||||
[ -x /sbin/wifi ] && /sbin/wifi config >/dev/null 2>&1
|
[ -x /sbin/wifi ] && /sbin/wifi config >/dev/null 2>&1
|
||||||
elif [ $phy_count -eq 0 ] && [ $uci_count -eq 0 ] && [ ! -s /etc/config/wireless ]; then
|
elif [ $phy_count -eq 0 ] && [ $uci_count -eq 0 ] && [ ! -s /etc/config/wireless ]; then
|
||||||
[ -x /sbin/wifi ] && /sbin/wifi config >/dev/null 2>&1
|
[ -x /sbin/wifi ] && /sbin/wifi config >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 重新同步一次 UCI radio 数量
|
# 重新同步 UCI radio 数量
|
||||||
if [ -s /etc/config/wireless ]; then
|
if [ -s /etc/config/wireless ]; then
|
||||||
local uci_radios_check="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
local uci_radios_check="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
||||||
[ -n "$uci_radios_check" ] && uci_count=$(echo "$uci_radios_check" | wc -w)
|
[ -n "$uci_radios_check" ] && uci_count=$(echo "$uci_radios_check" | wc -w)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# E. 判定就绪标准:
|
# 就绪标准判定
|
||||||
# 1) UCI 中有效 wifi-device 数量 > 0
|
|
||||||
# 2) 若检测到物理 PHY,UCI radio 数量必须完全覆盖检测到的 PHY 数量
|
|
||||||
if [ $uci_count -gt 0 ]; then
|
if [ $uci_count -gt 0 ]; then
|
||||||
if [ $phy_count -eq 0 ] || [ $uci_count -ge $phy_count ]; then
|
if [ $phy_count -eq 0 ] || [ $uci_count -ge $phy_count ]; then
|
||||||
return 0
|
return 0
|
||||||
@@ -122,20 +218,18 @@ wait_wifi_ready() {
|
|||||||
waited=$((waited + 1))
|
waited=$((waited + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
# 超时保护:只要至少有一个 radio 节点,允许降级继续
|
[ $uci_count -gt 0 ] && return 0
|
||||||
if [ $uci_count -gt 0 ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# 执行深度就绪等待
|
|
||||||
if ! wait_wifi_ready; then
|
if ! wait_wifi_ready; then
|
||||||
echo "[ERROR] 未检测到可用无线硬件或无线配置生成超时!" >&2
|
echo "[ERROR] 未检测到可用无线硬件或无线配置生成超时!" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3. 识别射频频段函数 (2g, 5g, 6g)
|
# -------------------------------------------------------------
|
||||||
|
# 6. 频段识别函数 (2g, 5g, 6g)
|
||||||
|
# -------------------------------------------------------------
|
||||||
detect_band() {
|
detect_band() {
|
||||||
local radio="$1"
|
local radio="$1"
|
||||||
local band="$(uci -q get wireless.${radio}.band 2>/dev/null | tr 'A-Z' 'a-z')"
|
local band="$(uci -q get wireless.${radio}.band 2>/dev/null | tr 'A-Z' 'a-z')"
|
||||||
@@ -146,7 +240,6 @@ detect_band() {
|
|||||||
*6g*) echo "6g"; return ;;
|
*6g*) echo "6g"; return ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# 信道 (channel) 推断
|
|
||||||
local channel="$(uci -q get wireless.${radio}.channel 2>/dev/null)"
|
local channel="$(uci -q get wireless.${radio}.channel 2>/dev/null)"
|
||||||
if [ -n "$channel" ] && [ "$channel" -eq "$channel" 2>/dev/null ]; then
|
if [ -n "$channel" ] && [ "$channel" -eq "$channel" 2>/dev/null ]; then
|
||||||
if [ "$channel" -ge 1 ] && [ "$channel" -le 14 ]; then
|
if [ "$channel" -ge 1 ] && [ "$channel" -le 14 ]; then
|
||||||
@@ -158,14 +251,12 @@ detect_band() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# hwmode 推断
|
|
||||||
local hwmode="$(uci -q get wireless.${radio}.hwmode 2>/dev/null | tr 'A-Z' 'a-z')"
|
local hwmode="$(uci -q get wireless.${radio}.hwmode 2>/dev/null | tr 'A-Z' 'a-z')"
|
||||||
case "$hwmode" in
|
case "$hwmode" in
|
||||||
*a*) echo "5g"; return ;;
|
*a*) echo "5g"; return ;;
|
||||||
*b*|*g*) echo "2g"; return ;;
|
*b*|*g*) echo "2g"; return ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# 双频常规配对推断
|
|
||||||
if [ "$radio" = "radio0" ]; then
|
if [ "$radio" = "radio0" ]; then
|
||||||
local r1_band="$(uci -q get wireless.radio1.band 2>/dev/null | tr 'A-Z' 'a-z')"
|
local r1_band="$(uci -q get wireless.radio1.band 2>/dev/null | tr 'A-Z' 'a-z')"
|
||||||
[ "$r1_band" = "5g" ] && echo "2g" && return
|
[ "$r1_band" = "5g" ] && echo "2g" && return
|
||||||
@@ -177,33 +268,31 @@ detect_band() {
|
|||||||
echo "2g"
|
echo "2g"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 4. 加载系统发行版信息(若存在)
|
# -------------------------------------------------------------
|
||||||
|
# 7. 配置 SSID 与各频段接口
|
||||||
|
# -------------------------------------------------------------
|
||||||
[ -f /etc/openwrt_release ] && . /etc/openwrt_release
|
[ -f /etc/openwrt_release ] && . /etc/openwrt_release
|
||||||
|
|
||||||
# 5. 确定基础 WiFi 名称
|
|
||||||
if [ -n "$CUSTOM_SSID" ]; then
|
if [ -n "$CUSTOM_SSID" ]; then
|
||||||
SSID="$CUSTOM_SSID"
|
SSID="$CUSTOM_SSID"
|
||||||
else
|
else
|
||||||
SSID="$(uci -q get system.@system[0].hostname 2>/dev/null)"
|
SSID="$(uci -q get system.@system[0].hostname 2>/dev/null)"
|
||||||
[ -z "$SSID" ] && [ -r /proc/sys/kernel/hostname ] && SSID="$(cat /proc/sys/kernel/hostname)"
|
[ -z "$SSID" ] && [ -r /proc/sys/kernel/hostname ] && SSID="$(cat /proc/sys/kernel/hostname)"
|
||||||
[ -z "$SSID" ] && SSID="Kwrt"
|
[ -z "$SSID" ] && SSID="$(uci -q get wizard.default.wifi_ssid 2>/dev/null)"
|
||||||
|
[ -z "$SSID" ] && SSID="OpenWrt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 移除禁用标记
|
|
||||||
sed -i '/option disabled/d' /etc/config/wireless 2>/dev/null
|
sed -i '/option disabled/d' /etc/config/wireless 2>/dev/null
|
||||||
|
|
||||||
# 修正双频标记
|
|
||||||
if [ "$(uci -q get wireless.radio1.band 2>/dev/null)" = "5g" ]; then
|
if [ "$(uci -q get wireless.radio1.band 2>/dev/null)" = "5g" ]; then
|
||||||
[ -z "$(uci -q get wireless.radio0.band 2>/dev/null)" ] && uci -q set wireless.radio0.band="2g"
|
[ -z "$(uci -q get wireless.radio0.band 2>/dev/null)" ] && uci -q set wireless.radio0.band="2g"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 重命名匿名接口 section 为 default_radioX
|
|
||||||
widx=0
|
widx=0
|
||||||
while uci rename wireless.@wifi-iface[$widx]=default_radio$widx >/dev/null 2>&1; do
|
while uci rename wireless.@wifi-iface[$widx]=default_radio$widx >/dev/null 2>&1; do
|
||||||
widx=$((widx + 1))
|
widx=$((widx + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
# 6. 配置各 Radio 参数
|
|
||||||
wifi_setup_radio() {
|
wifi_setup_radio() {
|
||||||
local radio="$1"
|
local radio="$1"
|
||||||
|
|
||||||
@@ -223,7 +312,6 @@ wifi_setup_radio() {
|
|||||||
|
|
||||||
local full_ssid="${SSID}${ssid_suffix}"
|
local full_ssid="${SSID}${ssid_suffix}"
|
||||||
|
|
||||||
# 特定硬件架构适配(如高通 ipq60xx)
|
|
||||||
if [ "$band" = "5g" ]; then
|
if [ "$band" = "5g" ]; then
|
||||||
case "$DISTRIB_TARGET" in
|
case "$DISTRIB_TARGET" in
|
||||||
*ipq60xx*)
|
*ipq60xx*)
|
||||||
@@ -240,10 +328,8 @@ wifi_setup_radio() {
|
|||||||
uci -q set wireless.${iface}.network='lan'
|
uci -q set wireless.${iface}.network='lan'
|
||||||
uci -q set wireless.${iface}.ssid="${full_ssid}"
|
uci -q set wireless.${iface}.ssid="${full_ssid}"
|
||||||
|
|
||||||
# 密码与加密策略配置
|
|
||||||
if [ -n "$SSID_PASSWD" ]; then
|
if [ -n "$SSID_PASSWD" ]; then
|
||||||
if [ "$band" = "6g" ]; then
|
if [ "$band" = "6g" ]; then
|
||||||
# 6GHz 频段按标准强制要求 WPA3-SAE
|
|
||||||
uci -q set wireless.${iface}.encryption='sae'
|
uci -q set wireless.${iface}.encryption='sae'
|
||||||
else
|
else
|
||||||
uci -q set wireless.${iface}.encryption='psk2'
|
uci -q set wireless.${iface}.encryption='psk2'
|
||||||
@@ -257,7 +343,7 @@ wifi_setup_radio() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# 7. 动态遍历所有射频设备并应用设置
|
# 动态遍历所有射频设备并配置
|
||||||
radios="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
radios="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
|
||||||
[ -z "$radios" ] && radios="radio0 radio1 radio2 radio3"
|
[ -z "$radios" ] && radios="radio0 radio1 radio2 radio3"
|
||||||
|
|
||||||
@@ -265,10 +351,10 @@ for radio in $radios; do
|
|||||||
wifi_setup_radio "$radio"
|
wifi_setup_radio "$radio"
|
||||||
done
|
done
|
||||||
|
|
||||||
# 提交配置
|
# 提交无线配置
|
||||||
uci commit wireless 2>/dev/null
|
uci commit wireless 2>/dev/null
|
||||||
|
|
||||||
# 若存在 base_config(kiddin9 固件状态记录),更新状态
|
# 标记 WiFi 基础配置已完成
|
||||||
if uci -q get base_config.@status[0] >/dev/null 2>&1; then
|
if uci -q get base_config.@status[0] >/dev/null 2>&1; then
|
||||||
uci -q set base_config.@status[0].wifi=1
|
uci -q set base_config.@status[0].wifi=1
|
||||||
uci commit base_config 2>/dev/null
|
uci commit base_config 2>/dev/null
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NPM_NAME:=pnpm
|
PKG_NPM_NAME:=pnpm
|
||||||
PKG_NAME:=node-$(PKG_NPM_NAME)
|
PKG_NAME:=node-$(PKG_NPM_NAME)
|
||||||
PKG_VERSION:=11.15.1
|
PKG_VERSION:=11.22.0
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NPM_NAME)-$(PKG_VERSION).tgz
|
PKG_SOURCE:=$(PKG_NPM_NAME)-$(PKG_VERSION).tgz
|
||||||
PKG_SOURCE_URL:=https://mirrors.tencent.com/npm/$(PKG_NPM_NAME)/-/ \
|
PKG_SOURCE_URL:=https://mirrors.tencent.com/npm/$(PKG_NPM_NAME)/-/ \
|
||||||
|
|||||||
+3
-3
@@ -50,12 +50,12 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=wwand
|
PKG_NAME:=wwand
|
||||||
PKG_VERSION:=1.6.7_p58
|
PKG_VERSION:=1.6.7_p60
|
||||||
PKG_RELEASE:=50
|
PKG_RELEASE:=51
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||||
PKG_SOURCE_VERSION:=1b575d41dd35684e035541afe1f129da072275dd
|
PKG_SOURCE_VERSION:=f5cba40880e0a11c00cf6b9040a6eb74e3d222da
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-2.0-only
|
PKG_LICENSE:=GPL-2.0-only
|
||||||
|
|||||||
+3
-2
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=xkcptun
|
PKG_NAME:=xkcptun
|
||||||
PKG_VERSION:=1.09.561
|
PKG_VERSION:=1.09.561
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/liudf0716/$(PKG_NAME)/tar.gz/$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/liudf0716/$(PKG_NAME)/tar.gz/$(PKG_VERSION)?
|
||||||
@@ -34,7 +34,8 @@ endef
|
|||||||
|
|
||||||
define Package/xkcptun/description
|
define Package/xkcptun/description
|
||||||
xkcptun is a lightweight and high-performance C language implementation of kcptun.
|
xkcptun is a lightweight and high-performance C language implementation of kcptun.
|
||||||
This package contains both client and server binaries.
|
This package contains the client and server binaries as well as the
|
||||||
|
xkcp_spy monitoring tool.
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/xkcptun/conffiles
|
define Package/xkcptun/conffiles
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
config global 'client'
|
config global 'client'
|
||||||
|
option enabled '0'
|
||||||
option remote_addr '1.2.3.4'
|
option remote_addr '1.2.3.4'
|
||||||
option remote_port '9089'
|
option remote_port '9089'
|
||||||
option mode 'fast3'
|
option mode 'fast3'
|
||||||
@@ -19,10 +20,10 @@ config global 'client'
|
|||||||
option sockbuf '16777216'
|
option sockbuf '16777216'
|
||||||
option keepalive '10'
|
option keepalive '10'
|
||||||
option conntimeout '60'
|
option conntimeout '60'
|
||||||
option key '7f7ac683d720e1437019692eadb0b811'
|
option key 'change_me'
|
||||||
|
|
||||||
config client 'client1'
|
config client 'client1'
|
||||||
option disabled '1'
|
option enabled '0'
|
||||||
option name 'ssh_tunnel'
|
option name 'ssh_tunnel'
|
||||||
option local_interface 'br-lan'
|
option local_interface 'br-lan'
|
||||||
option local_port '9088'
|
option local_port '9088'
|
||||||
@@ -50,4 +51,4 @@ config global 'server'
|
|||||||
option sockbuf '16777216'
|
option sockbuf '16777216'
|
||||||
option keepalive '10'
|
option keepalive '10'
|
||||||
option conntimeout '60'
|
option conntimeout '60'
|
||||||
option key '7f7ac683d720e1437019692eadb0b811'
|
option key 'change_me'
|
||||||
+19
-12
@@ -53,7 +53,7 @@ append_toml_tunnel() {
|
|||||||
|
|
||||||
eval "$("validate_${cfgtype}_section" "$cfg" validate_mklocal)"
|
eval "$("validate_${cfgtype}_section" "$cfg" validate_mklocal)"
|
||||||
"validate_${cfgtype}_section" "$cfg" || return 1
|
"validate_${cfgtype}_section" "$cfg" || return 1
|
||||||
[ "$disabled" = 0 ] || return 1
|
[ "${enabled:-1}" -eq 1 ] || return 1
|
||||||
[ -n "$local_port" ] || return 1
|
[ -n "$local_port" ] || return 1
|
||||||
|
|
||||||
cat << EOF >> "$outfile"
|
cat << EOF >> "$outfile"
|
||||||
@@ -128,15 +128,19 @@ EOF
|
|||||||
|
|
||||||
config_foreach append_section "$cfgtype"
|
config_foreach append_section "$cfgtype"
|
||||||
|
|
||||||
if [ "$cfgtype" = "client" ] && [ "$count" -eq 0 ]; then
|
if [ "$cfgtype" = "client" ]; then
|
||||||
rm -f "$conftoml"
|
local cli_enabled
|
||||||
return
|
config_get_bool cli_enabled client enabled 0
|
||||||
|
if [ "$cli_enabled" -ne 1 ] || [ "$count" -eq 0 ]; then
|
||||||
|
rm -f "$conftoml"
|
||||||
|
return
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$cfgtype" = "server" ]; then
|
if [ "$cfgtype" = "server" ]; then
|
||||||
local srv_enabled
|
local srv_enabled
|
||||||
config_get_bool srv_enabled server enabled 0
|
config_get_bool srv_enabled server enabled 0
|
||||||
if [ "$srv_enabled" -ne 1 ] && [ "$count" -eq 0 ]; then
|
if [ "$srv_enabled" -ne 1 ]; then
|
||||||
rm -f "$conftoml"
|
rm -f "$conftoml"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -151,6 +155,9 @@ EOF
|
|||||||
|
|
||||||
start_service() {
|
start_service() {
|
||||||
mkdir -p "$confdir"
|
mkdir -p "$confdir"
|
||||||
|
# generated TOML files contain the pre-shared key: keep them private
|
||||||
|
chmod 0700 "$confdir"
|
||||||
|
umask 077
|
||||||
config_load xkcptun
|
config_load xkcptun
|
||||||
start_daemon server
|
start_daemon server
|
||||||
start_daemon client
|
start_daemon client
|
||||||
@@ -189,10 +196,11 @@ validate_common_options() {
|
|||||||
local cfgtype="$1"; shift
|
local cfgtype="$1"; shift
|
||||||
local cfg="$1"; shift
|
local cfg="$1"; shift
|
||||||
local func="$1"; shift
|
local func="$1"; shift
|
||||||
|
local enabled_def="${1:-1}"; shift
|
||||||
local mode_profiles='"fast3", "fast2", "fast", "normal", "manual"'
|
local mode_profiles='"fast3", "fast2", "fast", "normal", "manual"'
|
||||||
|
|
||||||
"${func:-validate}" "$cfgtype" "$cfg" "$@" \
|
"${func:-validate}" "$cfgtype" "$cfg" "$@" \
|
||||||
'disabled:bool:0' \
|
"enabled:bool:$enabled_def" \
|
||||||
'name:string' \
|
'name:string' \
|
||||||
"mode:or($mode_profiles)" \
|
"mode:or($mode_profiles)" \
|
||||||
'mtu:uinteger' \
|
'mtu:uinteger' \
|
||||||
@@ -217,8 +225,7 @@ validate_common_options() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validate_global_options() {
|
validate_global_options() {
|
||||||
validate_common_options global "$1" "$2" \
|
validate_common_options global "$1" "$2" 0 \
|
||||||
'enabled:bool:0' \
|
|
||||||
'remote_addr:host' \
|
'remote_addr:host' \
|
||||||
'remote_port:port' \
|
'remote_port:port' \
|
||||||
'local_interface:string' \
|
'local_interface:string' \
|
||||||
@@ -226,15 +233,15 @@ validate_global_options() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validate_server_options() {
|
validate_server_options() {
|
||||||
validate_common_options server "$1" "$2" \
|
validate_common_options server "$1" "$2" 1 \
|
||||||
'local_interface:string:eth0' \
|
'local_interface:string:eth0' \
|
||||||
'local_port:port:9089' \
|
'local_port:port:9089' \
|
||||||
'remote_addr:host:127.0.0.1' \
|
'target_addr:host' \
|
||||||
'remote_port:port:443'
|
'target_port:port'
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_client_options() {
|
validate_client_options() {
|
||||||
validate_common_options client "$1" "$2" \
|
validate_common_options client "$1" "$2" 1 \
|
||||||
'local_interface:string:br-lan' \
|
'local_interface:string:br-lan' \
|
||||||
'local_port:port:9088' \
|
'local_port:port:9088' \
|
||||||
'proto:or("tcp", "udp")' \
|
'proto:or("tcp", "udp")' \
|
||||||
|
|||||||
Reference in new issue
Block a user