web: unify Agent management and add a draggable link board

Replace Probe pages with directed Agent links, shared deployment controls and unified display data. Add node dragging, canvas pan and zoom, saved layouts, and per-direction link controls.
This commit is contained in:
justme committed 2026-10-21 11:55:50 +08:00
1 parent 016dbc2bbf
commit 15f0023614
32 files changed
+937 -1093

No files matched your search

+11 -10
View File
@@ -3,12 +3,12 @@ import { fmtTime } from '@shared/format'
import { CommandTabs, CopyButton, Dialog, Field, guessPlatform, type CommandTab } from '@shared/ui'
/**
* 加入令牌创建之后的对话框:Agent 和 Probe 共用同一份。
* 加入令牌创建之后的对话框:Agent 共用同一份。
* Podman 系(Linux)首选 Quadlet(Podman 4.6+):env、单元文件(核对哈希)、启动用户服务;单元 Pull=always,节点发现新版本后自己退出重启,所以默认自动更新。
* Docker(Linux、macOS、Windows)用 compose:把面板给的 .env 存下来,下载 compose.yaml,`compose up -d`;compose 不会自动更新,手动 `compose pull && compose up -d`。
* env/.env 里只有公开的值(面板地址、一次性码、信任根指纹和属主),没有任何私钥;一行安装脚本留作备用。
*/
export function JoinTokenCreated({ created, kind, onClose }: { created: CreatedJoinTokenDto; kind: 'Agent' | 'Probe'; onClose: () => void }) {
export function JoinTokenCreated({ created, onClose }: { created: CreatedJoinTokenDto; onClose: () => void }) {
const quadlet = created.quadlet
const compose = created.compose
const script = created.install_commands
@@ -20,7 +20,7 @@ export function JoinTokenCreated({ created, kind, onClose }: { created: CreatedJ
const initial = composeTabs.some((t) => t.id === guessPlatform()) ? guessPlatform() : 'linux'
return (
<Dialog
title="加入令牌已创建"
title="部署 Agent"
onClose={onClose}
actions={
<button type="button" className="btn primary" onClick={onClose}>
@@ -29,17 +29,18 @@ export function JoinTokenCreated({ created, kind, onClose }: { created: CreatedJ
}
>
<div className="stack">
<p className="muted">所有平台使用同一种 Agent,默认同时监听和拨号。加入后自动与其他可达 Agent 相互探测,可在线路矩阵中按方向调整。</p>
<div className="notice warn">一次性码只显示这一次,请现在就复制。下面只有公开的值(面板地址、一次性码、信任根指纹和属主),没有任何私钥。</div>
{kind === 'Probe' && script.openwrt && (
{script.openwrt && (
<Field
label="OpenWrt · 原生二进制(自动更新)"
hint="以 root 运行,支持 ARM64 和 x86_64,不需要 Docker/Podman。通过 curl 或 OpenWrt 自带的 uclient-fetch 下载,缺少依赖时自动用 opkg/apk 安装。动态 musl 正式包需 libopenssl3。自动注册属主并配置 procd;发布后自动验签、安装,失败回滚。默认限制 20 Mbps,可在 /etc/moshiro/probe.json 修改。"
hint="以 root 运行,支持 ARM64 和 x86_64,不需要 Docker/Podman。通过 curl 或 OpenWrt 自带的 uclient-fetch 下载,缺少依赖时自动用 opkg/apk 安装。动态 musl 正式包需 libopenssl3。自动注册属主并配置 procd;发布后自动验签、安装,失败回滚。默认限制 20 Mbps,可在 /etc/moshiro/agent.json 修改。"
>
<CommandTabs storageKey="moshiro.openwrt_tab" initial="openwrt" tabs={[{ id: 'openwrt', label: 'OpenWrt · procd', command: script.openwrt }]} />
</Field>
)}
<Field
label={`Podman(推荐,自动更新):在要加入的机器上运行(${kind})`}
label={`Linux · Podman(推荐,自动更新)`}
hint="Linux,Podman 4.6 及以上,用普通用户运行(rootless,不用 sudo)。命令把参数写进 ~/.config/moshiro/(权限 600),下载 Quadlet 单元并核对 SHA-256,一致才启动。退出登录后继续运行需要管理员执行一次 loginctl enable-linger 用户名。节点名字默认取主机名;想自己定,在 env 里加一行 MOSHIRO_NAME=名字(1–64 个字母、数字、点、下划线、横线)。"
>
<CommandTabs storageKey="moshiro.quadlet_tab" initial="linux" tabs={[{ id: 'linux', label: 'Linux · Podman Quadlet', command: quadlet.command }]} />
@@ -49,7 +50,7 @@ export function JoinTokenCreated({ created, kind, onClose }: { created: CreatedJ
想改成在管理端点「更新」才更新,把 env 里 <span className="mono"># MOSHIRO_UPDATE_POLICY=manual</span> 的 # 去掉。固定版本:把单元里的 <span className="mono">:latest</span> 换成版本号。
</p>
<Field
label="Docker(Linux、macOS、Windows):compose"
label="Linux · Docker Compose"
hint="需要 docker compose(或 podman compose)。命令建一个目录,把参数写进 .env(权限 600),下载 compose.yaml 并核对 SHA-256,一致才启动。compose 不会自动更新(容器自己没法重新拉取):手动 compose pull && compose up -d。"
>
<CommandTabs storageKey="moshiro.compose_tab" initial={initial} tabs={composeTabs} />
@@ -57,7 +58,7 @@ export function JoinTokenCreated({ created, kind, onClose }: { created: CreatedJ
<details>
<summary>分步做:Quadlet 的 env 和单元,compose 的 .env 和 compose.yaml</summary>
<div className="stack" style={{ marginTop: 8 }}>
<Field label="Quadlet env(存成 ~/.config/moshiro/ 下的 agent.env 或 probe.env)">
<Field label="Quadlet env(存成 ~/.config/moshiro/ 下的 agent.env)">
<div className="row" style={{ alignItems: 'flex-start', gap: 8 }}>
<pre className="code" style={{ flex: 1, whiteSpace: 'pre-wrap', wordBreak: 'break-all', margin: 0 }}>{quadlet.env}</pre>
<CopyButton text={quadlet.env} />
@@ -92,11 +93,11 @@ export function JoinTokenCreated({ created, kind, onClose }: { created: CreatedJ
<p className="muted">
compose 更新:<span className="mono">podman compose pull &amp;&amp; podman compose up -d</span>(Docker 把 podman 换成 docker);身份和配置在命名卷里,不会动。
注册成功之后,可以把 env 或 .env 里的 <span className="mono">MOSHIRO_JOIN</span> 一行删掉。
{kind === 'Agent' ? (created.token.auto_approve ? ' 使用它注册的 Agent 会自动启用。' : ' 使用它注册的 Agent 需要你在列表里审批之后才会启用。') : ''}
{created.token.auto_approve ? ' 使用它注册的 Agent 会自动启用。' : ' 使用它注册的 Agent 需要你在列表里审批之后才会启用。'}
可用 {created.token.max_uses} 次,{fmtTime(created.token.expires_at)} 过期。
</p>
<details>
<summary>备用:一行安装脚本(先核对脚本哈希再执行,需要 Docker 或 Podman)</summary>
<summary>一键安装脚本 · Linux / macOS / Windows</summary>
<div style={{ marginTop: 8 }}>
<CommandTabs
storageKey="moshiro.install_tab"