feat: surface node p2p runtime visibility

This commit is contained in:
lpf
2026-03-08 22:53:03 +08:00
parent daaac53f5a
commit 29729d7c70
10 changed files with 168 additions and 24 deletions

View File

@@ -9,6 +9,7 @@ type RuntimeSnapshot = {
nodes?: {
nodes?: any[];
trees?: any[];
p2p?: Record<string, any>;
};
sessions?: {
sessions?: Array<{ key: string; title?: string; channel?: string }>;
@@ -43,6 +44,8 @@ interface AppContextType {
setNodes: (nodes: string) => void;
nodeTrees: string;
setNodeTrees: (trees: string) => void;
nodeP2P: Record<string, any>;
setNodeP2P: React.Dispatch<React.SetStateAction<Record<string, any>>>;
cron: CronJob[];
setCron: (cron: CronJob[]) => void;
skills: Skill[];
@@ -103,6 +106,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
const [configEditing, setConfigEditing] = useState(false);
const [nodes, setNodes] = useState('[]');
const [nodeTrees, setNodeTrees] = useState('[]');
const [nodeP2P, setNodeP2P] = useState<Record<string, any>>({});
const [cron, setCron] = useState<CronJob[]>([]);
const [skills, setSkills] = useState<Skill[]>([]);
const [clawhubInstalled, setClawhubInstalled] = useState(false);
@@ -161,6 +165,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
const j = await r.json();
setNodes(JSON.stringify(j.nodes || [], null, 2));
setNodeTrees(JSON.stringify(j.trees || [], null, 2));
setNodeP2P(j.p2p || {});
setIsGatewayOnline(true);
} catch (e) {
setIsGatewayOnline(false);
@@ -265,6 +270,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
if (snapshot.nodes) {
setNodes(JSON.stringify(Array.isArray(snapshot.nodes.nodes) ? snapshot.nodes.nodes : [], null, 2));
setNodeTrees(JSON.stringify(Array.isArray(snapshot.nodes.trees) ? snapshot.nodes.trees : [], null, 2));
setNodeP2P(snapshot.nodes.p2p || {});
}
if (snapshot.sessions) {
const arr = Array.isArray(snapshot.sessions.sessions) ? snapshot.sessions.sessions : [];
@@ -343,7 +349,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
return (
<AppContext.Provider value={{
token, setToken, sidebarOpen, setSidebarOpen, sidebarCollapsed, setSidebarCollapsed, isGatewayOnline, setIsGatewayOnline,
cfg, setCfg, cfgRaw, setCfgRaw, configEditing, setConfigEditing, nodes, setNodes, nodeTrees, setNodeTrees,
cfg, setCfg, cfgRaw, setCfgRaw, configEditing, setConfigEditing, nodes, setNodes, nodeTrees, setNodeTrees, nodeP2P, setNodeP2P,
cron, setCron, skills, setSkills, clawhubInstalled, clawhubPath,
sessions, setSessions,
taskQueueItems, setTaskQueueItems, ekgSummary, setEkgSummary,

View File

@@ -24,6 +24,7 @@ const resources = {
tasks: 'Tasks',
subagentProfiles: 'Subagent Profiles',
subagentsRuntime: 'Agents',
nodeP2P: 'Node P2P',
agentTopology: 'Agent Topology',
agentTopologyHint: 'Unified graph for local agents, registered nodes, and mirrored remote agent branches.',
runningTasks: 'running',
@@ -549,6 +550,7 @@ const resources = {
tasks: '任务管理',
subagentProfiles: '子代理档案',
subagentsRuntime: 'Agents',
nodeP2P: '节点 P2P',
agentTopology: 'Agent 拓扑',
agentTopologyHint: '统一展示本地 agent、注册 node 以及远端镜像 agent 分支的关系图。',
runningTasks: '运行中',

View File

@@ -14,6 +14,7 @@ const Dashboard: React.FC = () => {
webuiVersion,
skills,
cfg,
nodeP2P,
taskQueueItems,
ekgSummary,
} = useAppContext();
@@ -36,6 +37,9 @@ const Dashboard: React.FC = () => {
const ekgEscalationCount = Number(ekgSummary?.escalation_count || 0);
const ekgTopProvider = (Array.isArray(ekgSummary?.provider_top_workload) ? ekgSummary.provider_top_workload[0]?.key : '') || '-';
const ekgTopErrSig = (Array.isArray(ekgSummary?.errsig_top_workload) ? ekgSummary.errsig_top_workload[0]?.key : '') || '-';
const p2pEnabled = Boolean(nodeP2P?.enabled);
const p2pTransport = String(nodeP2P?.transport || (p2pEnabled ? 'enabled' : 'disabled'));
const p2pSessions = Number(nodeP2P?.active_sessions || 0);
return (
<div className="p-4 md:p-6 xl:p-8 w-full space-y-6 xl:space-y-8">
@@ -53,12 +57,13 @@ const Dashboard: React.FC = () => {
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-5 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-6 gap-4">
<StatCard title={t('gatewayStatus')} value={isGatewayOnline ? t('online') : t('offline')} icon={<Activity className={`w-6 h-6 ${isGatewayOnline ? 'text-emerald-400' : 'text-red-400'}`} />} />
<StatCard title={t('activeSessions')} value={sessions.length} icon={<MessageSquare className="w-6 h-6 text-blue-400" />} />
<StatCard title={t('skills')} value={skills.length} icon={<Sparkles className="w-6 h-6 text-pink-400" />} />
<StatCard title={t('subagentsRuntime')} value={subagentCount} icon={<Wrench className="w-6 h-6 text-cyan-400" />} />
<StatCard title={t('taskAudit')} value={recentTasks.length} icon={<Activity className="w-6 h-6 text-amber-400" />} />
<StatCard title={t('nodeP2P')} value={p2pEnabled ? `${p2pSessions} · ${p2pTransport}` : t('disabled')} icon={<Workflow className="w-6 h-6 text-violet-400" />} />
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">