@sleep2agi/agent-network-dashboard 0.4.4-preview.0 → 0.4.5-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +6 -6
- package/.next/diagnostics/route-bundle-stats.json +106 -92
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page/build-manifest.json +3 -3
- package/.next/server/app/admin/page.js.nft.json +1 -1
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +14 -14
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page/build-manifest.json +3 -3
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +14 -14
- package/.next/server/app/login.segments/_full.segment.rsc +14 -14
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- package/.next/server/app/logs/page/build-manifest.json +3 -3
- package/.next/server/app/logs/page.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +14 -14
- package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- package/.next/server/app/messages/page/build-manifest.json +3 -3
- package/.next/server/app/messages/page.js.nft.json +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +14 -14
- package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- package/.next/server/app/node/page/build-manifest.json +3 -3
- package/.next/server/app/node/page.js.nft.json +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +14 -14
- package/.next/server/app/node.segments/_full.segment.rsc +14 -14
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- package/.next/server/app/nodes/page/build-manifest.json +3 -3
- package/.next/server/app/nodes/page.js.nft.json +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +14 -14
- package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- package/.next/server/app/page/build-manifest.json +3 -3
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page/build-manifest.json +3 -3
- package/.next/server/app/server-logs/page.js.nft.json +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +14 -14
- package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- package/.next/server/app/settings/networks/page/build-manifest.json +3 -3
- package/.next/server/app/settings/networks/page.js.nft.json +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings/page/build-manifest.json +3 -3
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page/build-manifest.json +3 -3
- package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +14 -14
- package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/app/tasks/[id]/page/build-manifest.json +3 -3
- package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page/build-manifest.json +3 -3
- package/.next/server/app/tasks/page.js.nft.json +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__013i9e9._.js +9 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__013i9e9._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__12mpoig._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__12mpoig._.js.map +1 -0
- package/.next/server/chunks/ssr/_0d-fuox._.js +3 -0
- package/.next/server/chunks/ssr/_0d-fuox._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0.0wpa~._.js +3 -0
- package/.next/server/chunks/ssr/app_0.0wpa~._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0dk5wao._.js +1 -1
- package/.next/server/chunks/ssr/app_0dk5wao._.js.map +1 -1
- package/.next/server/chunks/ssr/app_0et5yua._.js +3 -0
- package/.next/server/chunks/ssr/app_0et5yua._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0ff~kjj._.js +9 -0
- package/.next/server/chunks/ssr/app_0ff~kjj._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0izi28j._.js +3 -0
- package/.next/server/chunks/ssr/app_0izi28j._.js.map +1 -0
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +1 -1
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +1 -1
- package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js +1 -1
- package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js.map +1 -1
- package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +1 -1
- package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +1 -1
- package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js.map +1 -0
- package/.next/server/middleware-build-manifest.js +6 -6
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/0257pdz1-imal.js +2 -0
- package/.next/static/chunks/02flrewxlf_m_.js +1 -0
- package/.next/static/chunks/051gslfjka1kj.js +1 -0
- package/.next/static/chunks/06t_vzei8s9_h.js +1 -0
- package/.next/static/chunks/0e-.zgw81gela.js +31 -0
- package/.next/static/chunks/0e7c40p~wq665.js +1 -0
- package/.next/static/chunks/0g7m7zk.v7g6h.js +1 -0
- package/.next/static/chunks/0hyhubmh-__wg.js +1 -0
- package/.next/static/chunks/0khpyxpdrp~ty.js +1 -0
- package/.next/static/chunks/0lo_g.q1fm-hr.js +7 -0
- package/.next/static/chunks/0snd~q2ucn.h1.css +2 -0
- package/.next/static/chunks/0w1zwt8kq_7nb.js +1 -0
- package/.next/static/chunks/0xn9.0ejo1s_a.js +7 -0
- package/.next/static/chunks/0y0lo-~k2c~00.js +1 -0
- package/.next/static/chunks/13.dst7liziwo.js +1 -0
- package/.next/static/chunks/{turbopack-0vsvjs02l449s.js → turbopack-01f1xs~cq_~si.js} +1 -1
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/EmptyState.tsx +189 -0
- package/app/components/LoadingSkeleton.tsx +3 -18
- package/app/components/Sidebar.tsx +72 -11
- package/app/components/TopoGraph.tsx +151 -62
- package/app/globals.css +35 -0
- package/app/login/page.tsx +26 -13
- package/app/logs/page.tsx +2 -5
- package/app/messages/page.tsx +2 -5
- package/app/nodes/page.tsx +6 -1
- package/app/page.tsx +36 -12
- package/app/settings/networks/page.tsx +6 -1
- package/app/settings/page.tsx +56 -29
- package/app/settings/tokens/page.tsx +6 -1
- package/app/tasks/page.tsx +7 -9
- package/docs/loop-design-rounds.md +502 -0
- package/docs/oss-readiness-report.md +189 -0
- package/package.json +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__093ymui._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__093ymui._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0i1gizm._.js +0 -9
- package/.next/server/chunks/ssr/[root-of-the-server]__0i1gizm._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0~yad5w._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0~yad5w._.js.map +0 -1
- package/.next/server/chunks/ssr/_06_xh1~._.js +0 -3
- package/.next/server/chunks/ssr/_06_xh1~._.js.map +0 -1
- package/.next/server/chunks/ssr/app_01vhyfx._.js +0 -9
- package/.next/server/chunks/ssr/app_01vhyfx._.js.map +0 -1
- package/.next/server/chunks/ssr/app_023a94i._.js +0 -3
- package/.next/server/chunks/ssr/app_023a94i._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0basiqb._.js +0 -3
- package/.next/server/chunks/ssr/app_0basiqb._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0pljk5o._.js +0 -3
- package/.next/server/chunks/ssr/app_0pljk5o._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js.map +0 -1
- package/.next/static/chunks/0.z~9klrgd.t-.js +0 -1
- package/.next/static/chunks/08aj9~75raaah.js +0 -7
- package/.next/static/chunks/08iom_z8w-wmz.js +0 -7
- package/.next/static/chunks/08v0-ki6vwpxf.js +0 -1
- package/.next/static/chunks/0dkkv90do9a4r.js +0 -1
- package/.next/static/chunks/0e-wrhqh4h3r5.js +0 -2
- package/.next/static/chunks/0ejz4zwhu3a3y.js +0 -1
- package/.next/static/chunks/0o5zj~5vjeks3.js +0 -1
- package/.next/static/chunks/0uqfv6rc-xdru.js +0 -1
- package/.next/static/chunks/0u~_.32f.midj.css +0 -2
- package/.next/static/chunks/0xrnwfefpke-g.js +0 -31
- package/.next/static/chunks/0y_po~f~fk~ap.js +0 -1
- package/.next/static/chunks/14sm.ta0zylsj.js +0 -1
- package/.next/static/chunks/18ac93ii3mjlj.js +0 -1
- /package/.next/static/{jTh5m77FIfQccc0gftvik → 2m1yIfZCIYDWMYrrJEKSC}/_buildManifest.js +0 -0
- /package/.next/static/{jTh5m77FIfQccc0gftvik → 2m1yIfZCIYDWMYrrJEKSC}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{jTh5m77FIfQccc0gftvik → 2m1yIfZCIYDWMYrrJEKSC}/_ssgManifest.js +0 -0
|
@@ -45,21 +45,6 @@ export function LoadingSkeleton() {
|
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<div className="text-gray-600 text-4xl mb-4">--</div>
|
|
52
|
-
<h3 className="text-gray-400 text-lg font-medium mb-2">No agents in this network</h3>
|
|
53
|
-
{hint?.global_count ? (
|
|
54
|
-
<p className="text-gray-500 text-sm max-w-md mx-auto">
|
|
55
|
-
Server has <span className="text-cyan-400 font-medium">{hint.global_count}</span> nodes globally,
|
|
56
|
-
but none are registered to the current network. Switch network or contact admin.
|
|
57
|
-
</p>
|
|
58
|
-
) : (
|
|
59
|
-
<p className="text-gray-600 text-sm max-w-md mx-auto">
|
|
60
|
-
Agent sessions will appear here once they connect to the CommHub.
|
|
61
|
-
</p>
|
|
62
|
-
)}
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
48
|
+
// EmptyState export removed in 0.4.5 — replaced by EmptyState.tsx with
|
|
49
|
+
// per-variant glyphs and a NodesEmptyState wrapper for the Overview
|
|
50
|
+
// hint-aware behavior. See app/components/EmptyState.tsx.
|
|
@@ -101,17 +101,11 @@ export function Sidebar() {
|
|
|
101
101
|
${mobileOpen ? 'translate-x-0' : '-translate-x-full'}
|
|
102
102
|
lg:translate-x-0 lg:static lg:shrink-0
|
|
103
103
|
`}>
|
|
104
|
-
{/*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<div>
|
|
110
|
-
<div className="text-white font-bold text-sm">Agent Network</div>
|
|
111
|
-
<div className="text-gray-600 text-xs">Dashboard</div>
|
|
112
|
-
</div>
|
|
113
|
-
)}
|
|
114
|
-
</div>
|
|
104
|
+
{/* Brand header — round 4: 3-node mesh mark matches /login,
|
|
105
|
+
with an inline live "online" pulse so every page surfaces
|
|
106
|
+
fleet health without leaving for /nodes. */}
|
|
107
|
+
<SidebarBrand collapsed={collapsed} />
|
|
108
|
+
<div className={`border-b border-[#2a2a4a]`} />
|
|
115
109
|
|
|
116
110
|
{/* Network list */}
|
|
117
111
|
{!collapsed && networks.length > 0 && (
|
|
@@ -174,3 +168,70 @@ export function Sidebar() {
|
|
|
174
168
|
</>
|
|
175
169
|
);
|
|
176
170
|
}
|
|
171
|
+
|
|
172
|
+
interface StatusData {
|
|
173
|
+
sessions?: Array<{ status?: string; alias?: string }>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const statusFetcher = (url: string) =>
|
|
177
|
+
fetch(url).then(r => r.ok ? r.json() as Promise<StatusData> : { sessions: [] });
|
|
178
|
+
|
|
179
|
+
function SidebarBrand({ collapsed }: { collapsed: boolean }) {
|
|
180
|
+
// Live online count — re-uses the same /api/hub/status endpoint the
|
|
181
|
+
// Overview already polls, so SWR dedupes the request. Pulse is muted —
|
|
182
|
+
// emerald dot at 4px with a 1.6s slow opacity pulse, no glow, no halo.
|
|
183
|
+
const { data } = useSWR<StatusData>('/api/hub/status', statusFetcher, {
|
|
184
|
+
refreshInterval: 10000,
|
|
185
|
+
dedupingInterval: 4000,
|
|
186
|
+
});
|
|
187
|
+
const sessions = data?.sessions || [];
|
|
188
|
+
const online = sessions.filter(s => s.status && s.status !== 'offline').length;
|
|
189
|
+
const total = sessions.length;
|
|
190
|
+
|
|
191
|
+
if (collapsed) {
|
|
192
|
+
return (
|
|
193
|
+
<Link href="/" className="block px-3 py-4 flex justify-center hover:opacity-80 transition-opacity" aria-label="Agent Network — home">
|
|
194
|
+
<BrandMark size={32} />
|
|
195
|
+
</Link>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<Link
|
|
201
|
+
href="/"
|
|
202
|
+
className="block px-4 py-4 flex items-center gap-3 hover:bg-[#11112a]/40 transition-colors rounded-r-xl"
|
|
203
|
+
aria-label="Agent Network — home"
|
|
204
|
+
>
|
|
205
|
+
<BrandMark size={32} />
|
|
206
|
+
<div className="min-w-0">
|
|
207
|
+
<div className="text-white text-[13px] font-semibold leading-tight">Agent Network</div>
|
|
208
|
+
<div className="text-[10px] text-gray-500 flex items-center gap-1.5 mt-0.5">
|
|
209
|
+
<span
|
|
210
|
+
className={`inline-block w-1.5 h-1.5 rounded-full ${online > 0 ? 'bg-emerald-400 anet-brand-pulse' : 'bg-gray-600'}`}
|
|
211
|
+
aria-hidden
|
|
212
|
+
/>
|
|
213
|
+
{total === 0 ? (
|
|
214
|
+
<span>waiting for agents</span>
|
|
215
|
+
) : (
|
|
216
|
+
<span><span className="text-gray-300 font-medium tabular-nums">{online}</span><span className="text-gray-600">/{total}</span> online</span>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</Link>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** 3-node mesh mark — same SVG as /login, reusable. */
|
|
225
|
+
function BrandMark({ size }: { size: number }) {
|
|
226
|
+
return (
|
|
227
|
+
<svg width={size} height={size} viewBox="0 0 32 32" aria-hidden className="shrink-0">
|
|
228
|
+
<circle cx="16" cy="16" r="10" fill="none" stroke="currentColor" strokeWidth="1.5" opacity="0.2" className="text-cyan-400" />
|
|
229
|
+
<line x1="16" y1="10" x2="10" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.45" className="text-cyan-400" />
|
|
230
|
+
<line x1="16" y1="10" x2="22" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.45" className="text-cyan-400" />
|
|
231
|
+
<line x1="10" y1="20" x2="22" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.45" className="text-cyan-400" />
|
|
232
|
+
<circle cx="16" cy="10" r="2.5" fill="currentColor" className="text-cyan-400" />
|
|
233
|
+
<circle cx="10" cy="20" r="2.5" fill="currentColor" className="text-green-400" />
|
|
234
|
+
<circle cx="22" cy="20" r="2.5" fill="currentColor" className="text-violet-400" />
|
|
235
|
+
</svg>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
@@ -63,42 +63,123 @@ function truncate(value: string, max: number) {
|
|
|
63
63
|
return value.length > max ? `${value.slice(0, max - 1)}...` : value;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function nodeStatus(session: Session, isOnline: boolean) {
|
|
66
|
+
function nodeStatus(session: Session, isOnline: boolean, isLight: boolean) {
|
|
67
67
|
if (!isOnline) {
|
|
68
68
|
return {
|
|
69
69
|
label: 'offline',
|
|
70
|
-
primary: '#6b7280',
|
|
71
|
-
halo: '#111827',
|
|
72
|
-
text: '#9ca3af',
|
|
70
|
+
primary: isLight ? '#94a3b8' : '#6b7280',
|
|
71
|
+
halo: isLight ? '#e2e8f0' : '#111827',
|
|
72
|
+
text: isLight ? '#475569' : '#9ca3af',
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
|
|
76
75
|
if (session.status === 'working') {
|
|
77
76
|
return {
|
|
78
77
|
label: 'working',
|
|
79
|
-
primary: '#22c55e',
|
|
80
|
-
halo: '#14532d',
|
|
81
|
-
text: '#dcfce7',
|
|
78
|
+
primary: isLight ? '#059669' : '#22c55e',
|
|
79
|
+
halo: isLight ? '#d1fae5' : '#14532d',
|
|
80
|
+
text: isLight ? '#065f46' : '#dcfce7',
|
|
82
81
|
};
|
|
83
82
|
}
|
|
84
|
-
|
|
85
83
|
if (session.status === 'idle') {
|
|
86
84
|
return {
|
|
87
85
|
label: 'idle',
|
|
88
|
-
primary: '#2dd4bf',
|
|
89
|
-
halo: '#134e4a',
|
|
90
|
-
text: '#ccfbf1',
|
|
86
|
+
primary: isLight ? '#0d9488' : '#2dd4bf',
|
|
87
|
+
halo: isLight ? '#ccfbf1' : '#134e4a',
|
|
88
|
+
text: isLight ? '#115e59' : '#ccfbf1',
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
|
-
|
|
94
91
|
return {
|
|
95
92
|
label: session.status || 'online',
|
|
96
|
-
primary: '#38bdf8',
|
|
97
|
-
halo: '#0c4a6e',
|
|
98
|
-
text: '#e0f2fe',
|
|
93
|
+
primary: isLight ? '#0284c7' : '#38bdf8',
|
|
94
|
+
halo: isLight ? '#dbeafe' : '#0c4a6e',
|
|
95
|
+
text: isLight ? '#0c4a6e' : '#e0f2fe',
|
|
99
96
|
};
|
|
100
97
|
}
|
|
101
98
|
|
|
99
|
+
/** Theme-aware color palette for the topology SVG. */
|
|
100
|
+
interface Palette {
|
|
101
|
+
panelStops: [string, string, string];
|
|
102
|
+
radarStops: { color: string; opacity: number }[];
|
|
103
|
+
arrowFill: string;
|
|
104
|
+
ringStroke: string;
|
|
105
|
+
spokeStroke: { active: string; idle: string };
|
|
106
|
+
flowEdge: string;
|
|
107
|
+
flowPath: string;
|
|
108
|
+
flowParticle: string;
|
|
109
|
+
nodeFill: { online: string; offline: string };
|
|
110
|
+
labelBox: { fill: string; stroke: string };
|
|
111
|
+
legendBox: { fill: string; stroke: string };
|
|
112
|
+
legendText: string;
|
|
113
|
+
legendHeadline: string;
|
|
114
|
+
legendAccent: string;
|
|
115
|
+
containerBg: string;
|
|
116
|
+
containerBorder: string;
|
|
117
|
+
topRailGradient: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const DARK_PALETTE: Palette = {
|
|
121
|
+
panelStops: ['#0b1220', '#080814', '#101018'],
|
|
122
|
+
radarStops: [
|
|
123
|
+
{ color: '#22d3ee', opacity: 0.18 },
|
|
124
|
+
{ color: '#22c55e', opacity: 0.045 },
|
|
125
|
+
{ color: '#020617', opacity: 0 },
|
|
126
|
+
],
|
|
127
|
+
arrowFill: '#67e8f9',
|
|
128
|
+
ringStroke: '#164e63',
|
|
129
|
+
spokeStroke: { active: '#22d3ee', idle: '#155e75' },
|
|
130
|
+
flowEdge: '#67e8f9',
|
|
131
|
+
flowPath: '#e0f2fe',
|
|
132
|
+
flowParticle: '#fef08a',
|
|
133
|
+
nodeFill: { online: '#020617', offline: '#080814' },
|
|
134
|
+
labelBox: { fill: '#020617', stroke: '#1f2937' },
|
|
135
|
+
legendBox: { fill: '#020617', stroke: '#1f2937' },
|
|
136
|
+
legendText: '#94a3b8',
|
|
137
|
+
legendHeadline: '#e5e7eb',
|
|
138
|
+
legendAccent: '#67e8f9',
|
|
139
|
+
containerBg: '#080814',
|
|
140
|
+
containerBorder: '#2a2a4a',
|
|
141
|
+
topRailGradient: 'from-transparent via-cyan-400/70 to-transparent',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const LIGHT_PALETTE: Palette = {
|
|
145
|
+
panelStops: ['#f8fafc', '#ffffff', '#f1f5f9'],
|
|
146
|
+
radarStops: [
|
|
147
|
+
{ color: '#10b981', opacity: 0.06 },
|
|
148
|
+
{ color: '#3b82f6', opacity: 0.03 },
|
|
149
|
+
{ color: '#ffffff', opacity: 0 },
|
|
150
|
+
],
|
|
151
|
+
arrowFill: '#10b981',
|
|
152
|
+
ringStroke: '#cbd5e1',
|
|
153
|
+
spokeStroke: { active: '#10b981', idle: '#cbd5e1' },
|
|
154
|
+
flowEdge: '#10b981',
|
|
155
|
+
flowPath: '#475569',
|
|
156
|
+
flowParticle: '#f59e0b',
|
|
157
|
+
nodeFill: { online: '#ffffff', offline: '#f8fafc' },
|
|
158
|
+
labelBox: { fill: '#ffffff', stroke: '#e2e8f0' },
|
|
159
|
+
legendBox: { fill: '#ffffff', stroke: '#e2e8f0' },
|
|
160
|
+
legendText: '#475569',
|
|
161
|
+
legendHeadline: '#0f172a',
|
|
162
|
+
legendAccent: '#0d9488',
|
|
163
|
+
containerBg: '#ffffff',
|
|
164
|
+
containerBorder: '#e3e6eb',
|
|
165
|
+
topRailGradient: 'from-transparent via-emerald-500/40 to-transparent',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
function useTheme(): 'light' | 'dark' {
|
|
169
|
+
const [theme, setTheme] = useState<'light' | 'dark'>('dark');
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
const read = () => {
|
|
172
|
+
const t = document.documentElement.getAttribute('data-theme') || 'cyber';
|
|
173
|
+
setTheme(t === 'light' || t === 'mint' ? 'light' : 'dark');
|
|
174
|
+
};
|
|
175
|
+
read();
|
|
176
|
+
const obs = new MutationObserver(read);
|
|
177
|
+
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
178
|
+
return () => obs.disconnect();
|
|
179
|
+
}, []);
|
|
180
|
+
return theme;
|
|
181
|
+
}
|
|
182
|
+
|
|
102
183
|
function buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point>) {
|
|
103
184
|
const links = new Map<string, FlowLink>();
|
|
104
185
|
|
|
@@ -127,6 +208,9 @@ function buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point
|
|
|
127
208
|
}
|
|
128
209
|
|
|
129
210
|
export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
211
|
+
const theme = useTheme();
|
|
212
|
+
const isLight = theme === 'light';
|
|
213
|
+
const pal = isLight ? LIGHT_PALETTE : DARK_PALETTE;
|
|
130
214
|
const [messages, setMessages] = useState<MessageFlow[]>([]);
|
|
131
215
|
|
|
132
216
|
useEffect(() => {
|
|
@@ -206,30 +290,35 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
206
290
|
</div>
|
|
207
291
|
</div>
|
|
208
292
|
|
|
209
|
-
<div
|
|
210
|
-
|
|
293
|
+
<div
|
|
294
|
+
className={`relative overflow-hidden rounded-lg border shadow-2xl ${isLight ? 'shadow-zinc-900/5' : 'shadow-cyan-950/30'}`}
|
|
295
|
+
style={{ background: pal.containerBg, borderColor: pal.containerBorder }}
|
|
296
|
+
>
|
|
297
|
+
<div className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r ${pal.topRailGradient}`} />
|
|
211
298
|
|
|
212
299
|
<svg viewBox="0 0 1000 680" className="w-full h-auto block" preserveAspectRatio="xMidYMid meet">
|
|
213
300
|
<defs>
|
|
214
301
|
<linearGradient id="topo-panel" x1="0" x2="1" y1="0" y2="1">
|
|
215
|
-
<stop offset="0%"
|
|
216
|
-
<stop offset="48%"
|
|
217
|
-
<stop offset="100%" stopColor=
|
|
302
|
+
<stop offset="0%" stopColor={pal.panelStops[0]} />
|
|
303
|
+
<stop offset="48%" stopColor={pal.panelStops[1]} />
|
|
304
|
+
<stop offset="100%" stopColor={pal.panelStops[2]} />
|
|
218
305
|
</linearGradient>
|
|
219
306
|
<radialGradient id="topo-radar" cx="50%" cy="50%" r="55%">
|
|
220
|
-
<stop offset="0%"
|
|
221
|
-
<stop offset="45%"
|
|
222
|
-
<stop offset="100%" stopColor=
|
|
307
|
+
<stop offset="0%" stopColor={pal.radarStops[0].color} stopOpacity={pal.radarStops[0].opacity} />
|
|
308
|
+
<stop offset="45%" stopColor={pal.radarStops[1].color} stopOpacity={pal.radarStops[1].opacity} />
|
|
309
|
+
<stop offset="100%" stopColor={pal.radarStops[2].color} stopOpacity={pal.radarStops[2].opacity} />
|
|
223
310
|
</radialGradient>
|
|
224
|
-
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
311
|
+
{!isLight && (
|
|
312
|
+
<filter id="topo-glow">
|
|
313
|
+
<feGaussianBlur stdDeviation="4" result="blur" />
|
|
314
|
+
<feMerge>
|
|
315
|
+
<feMergeNode in="blur" />
|
|
316
|
+
<feMergeNode in="SourceGraphic" />
|
|
317
|
+
</feMerge>
|
|
318
|
+
</filter>
|
|
319
|
+
)}
|
|
231
320
|
<marker id="topo-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
|
|
232
|
-
<path d="M 0 0 L 10 5 L 0 10 z" fill=
|
|
321
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill={pal.arrowFill} />
|
|
233
322
|
</marker>
|
|
234
323
|
</defs>
|
|
235
324
|
|
|
@@ -238,7 +327,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
238
327
|
|
|
239
328
|
{/* radar rings */}
|
|
240
329
|
{[90, 170, 250, 330].map(radius => (
|
|
241
|
-
<circle key={radius} cx={cx} cy={cy} r={radius} fill="none" stroke=
|
|
330
|
+
<circle key={radius} cx={cx} cy={cy} r={radius} fill="none" stroke={pal.ringStroke} strokeWidth="1" opacity={isLight ? 0.6 : 0.35} />
|
|
242
331
|
))}
|
|
243
332
|
{[0, 30, 60, 90, 120, 150].map(angle => (
|
|
244
333
|
<line
|
|
@@ -247,9 +336,9 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
247
336
|
y1={cy - 360 * Math.sin(angle * Math.PI / 180)}
|
|
248
337
|
x2={cx + 360 * Math.cos(angle * Math.PI / 180)}
|
|
249
338
|
y2={cy + 360 * Math.sin(angle * Math.PI / 180)}
|
|
250
|
-
stroke=
|
|
339
|
+
stroke={pal.ringStroke}
|
|
251
340
|
strokeWidth="1"
|
|
252
|
-
opacity=
|
|
341
|
+
opacity={isLight ? 0.35 : 0.18}
|
|
253
342
|
/>
|
|
254
343
|
))}
|
|
255
344
|
|
|
@@ -264,7 +353,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
264
353
|
key={`hub-${session.alias}`}
|
|
265
354
|
d={path}
|
|
266
355
|
fill="none"
|
|
267
|
-
stroke={activeAliases.has(session.alias) ?
|
|
356
|
+
stroke={activeAliases.has(session.alias) ? pal.spokeStroke.active : pal.spokeStroke.idle}
|
|
268
357
|
strokeWidth={activeAliases.has(session.alias) ? 2 : 1}
|
|
269
358
|
strokeDasharray={activeAliases.has(session.alias) ? 'none' : '8 10'}
|
|
270
359
|
opacity={activeAliases.has(session.alias) ? 0.65 : 0.35}
|
|
@@ -288,22 +377,22 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
288
377
|
<path
|
|
289
378
|
d={path}
|
|
290
379
|
fill="none"
|
|
291
|
-
stroke=
|
|
380
|
+
stroke={pal.flowEdge}
|
|
292
381
|
strokeWidth={width}
|
|
293
|
-
opacity=
|
|
294
|
-
filter=
|
|
382
|
+
opacity={isLight ? 0.22 : 0.28}
|
|
383
|
+
filter={isLight ? undefined : 'url(#topo-glow)'}
|
|
295
384
|
markerEnd="url(#topo-arrow)"
|
|
296
385
|
/>
|
|
297
386
|
<path
|
|
298
387
|
id={`flow-path-${index}`}
|
|
299
388
|
d={path}
|
|
300
389
|
fill="none"
|
|
301
|
-
stroke=
|
|
390
|
+
stroke={pal.flowPath}
|
|
302
391
|
strokeWidth="1"
|
|
303
392
|
strokeDasharray="2 12"
|
|
304
|
-
opacity=
|
|
393
|
+
opacity={isLight ? 0.4 : 0.75}
|
|
305
394
|
/>
|
|
306
|
-
<circle r="4" fill=
|
|
395
|
+
<circle r="4" fill={pal.flowParticle} filter={isLight ? undefined : 'url(#topo-glow)'}>
|
|
307
396
|
<animateMotion dur={`${duration}s`} repeatCount="indefinite" path={path} />
|
|
308
397
|
</circle>
|
|
309
398
|
</g>
|
|
@@ -340,38 +429,38 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
340
429
|
|
|
341
430
|
const sseCountFor = (session.network_id ? sseSessions[`${session.network_id}:${session.alias}`] : undefined) ?? sseSessions[session.alias];
|
|
342
431
|
const isOnline = session.status !== 'offline' || !!sseCountFor;
|
|
343
|
-
const status = nodeStatus(session, isOnline);
|
|
432
|
+
const status = nodeStatus(session, isOnline, isLight);
|
|
344
433
|
const isActive = activeAliases.has(session.alias);
|
|
345
434
|
const radius = isOnline ? 26 : 18;
|
|
346
435
|
|
|
347
436
|
return (
|
|
348
437
|
<g key={session.alias} className="transition-opacity">
|
|
349
438
|
{isActive && (
|
|
350
|
-
<circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity=
|
|
439
|
+
<circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity={isLight ? 0.08 : 0.12}>
|
|
351
440
|
<animate attributeName="r" values={`${radius + 8};${radius + 22};${radius + 8}`} dur="2.4s" repeatCount="indefinite" />
|
|
352
|
-
<animate attributeName="opacity" values=
|
|
441
|
+
<animate attributeName="opacity" values={isLight ? '0.12;0.02;0.12' : '0.18;0.04;0.18'} dur="2.4s" repeatCount="indefinite" />
|
|
353
442
|
</circle>
|
|
354
443
|
)}
|
|
355
|
-
<circle cx={pos.x} cy={pos.y} r={radius + 8} fill={status.halo} opacity={isOnline ? 0.55 : 0.25} />
|
|
444
|
+
<circle cx={pos.x} cy={pos.y} r={radius + 8} fill={status.halo} opacity={isOnline ? (isLight ? 0.85 : 0.55) : (isLight ? 0.4 : 0.25)} />
|
|
356
445
|
<circle
|
|
357
446
|
cx={pos.x}
|
|
358
447
|
cy={pos.y}
|
|
359
448
|
r={radius}
|
|
360
|
-
fill={isOnline ?
|
|
449
|
+
fill={isOnline ? pal.nodeFill.online : pal.nodeFill.offline}
|
|
361
450
|
stroke={status.primary}
|
|
362
451
|
strokeWidth={isOnline ? 3 : 1.5}
|
|
363
452
|
strokeDasharray={isOnline ? 'none' : '5 5'}
|
|
364
|
-
filter={isOnline ? 'url(#topo-glow)' : undefined}
|
|
453
|
+
filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}
|
|
365
454
|
/>
|
|
366
455
|
<circle cx={pos.x} cy={pos.y} r={isOnline ? 8 : 5} fill={status.primary} />
|
|
367
456
|
{session.status === 'working' && (
|
|
368
|
-
<path d={`M${pos.x - 9},${pos.y + 11} h18`} stroke=
|
|
457
|
+
<path d={`M${pos.x - 9},${pos.y + 11} h18`} stroke={pal.flowParticle} strokeWidth="3" strokeLinecap="round">
|
|
369
458
|
<animate attributeName="opacity" values="1;0.25;1" dur="1.1s" repeatCount="indefinite" />
|
|
370
459
|
</path>
|
|
371
460
|
)}
|
|
372
461
|
|
|
373
462
|
<g transform={`translate(${pos.x}, ${pos.y + radius + 22})`}>
|
|
374
|
-
<rect x="-62" y="-14" width="124" height="42" rx="6" fill=
|
|
463
|
+
<rect x="-62" y="-14" width="124" height="42" rx="6" fill={pal.labelBox.fill} stroke={pal.labelBox.stroke} opacity={isLight ? 1 : 0.94} />
|
|
375
464
|
<text x="0" y="1" textAnchor="middle" fill={status.text} fontSize="12" fontFamily="monospace" fontWeight="700">
|
|
376
465
|
{truncate(session.alias, 14)}
|
|
377
466
|
</text>
|
|
@@ -385,11 +474,11 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
385
474
|
|
|
386
475
|
{/* latest flow labels */}
|
|
387
476
|
<g transform="translate(28, 34)">
|
|
388
|
-
<rect x="0" y="0" width="285" height={60 + Math.min(flowLinks.length, 4) * 20} rx="8" fill=
|
|
389
|
-
<text x="16" y="26" fill=
|
|
390
|
-
<text x="178" y="26" fill=
|
|
477
|
+
<rect x="0" y="0" width="285" height={60 + Math.min(flowLinks.length, 4) * 20} rx="8" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 1 : 0.94} />
|
|
478
|
+
<text x="16" y="26" fill={pal.legendHeadline} fontSize="13" fontFamily="monospace" fontWeight="700">recent signal</text>
|
|
479
|
+
<text x="178" y="26" fill={pal.legendAccent} fontSize="11" fontFamily="monospace">{messages.length} messages</text>
|
|
391
480
|
{flowLinks.slice(0, 4).map((link, index) => (
|
|
392
|
-
<text key={link.key} x="16" y={54 + index * 20} fill=
|
|
481
|
+
<text key={link.key} x="16" y={54 + index * 20} fill={pal.legendText} fontSize="10" fontFamily="monospace">
|
|
393
482
|
{truncate(link.from, 8)} {'->'} {truncate(link.to, 8)} / {link.count} / {truncate(link.content, 18)}
|
|
394
483
|
</text>
|
|
395
484
|
))}
|
|
@@ -397,14 +486,14 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
397
486
|
|
|
398
487
|
{/* legend */}
|
|
399
488
|
<g transform="translate(720, 34)">
|
|
400
|
-
<rect x="0" y="0" width="250" height="112" rx="8" fill=
|
|
401
|
-
<circle cx="18" cy="26" r="6" fill=
|
|
402
|
-
<text x="34" y="30" fill=
|
|
403
|
-
<circle cx="18" cy="52" r="6" fill=
|
|
404
|
-
<text x="34" y="56" fill=
|
|
405
|
-
<circle cx="18" cy="78" r="6" fill=
|
|
406
|
-
<text x="34" y="82" fill=
|
|
407
|
-
<path d="M150,78 Q176,52 210,78" fill="none" stroke=
|
|
489
|
+
<rect x="0" y="0" width="250" height="112" rx="8" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 1 : 0.94} />
|
|
490
|
+
<circle cx="18" cy="26" r="6" fill={isLight ? '#059669' : '#22c55e'} />
|
|
491
|
+
<text x="34" y="30" fill={pal.legendText} fontSize="11" fontFamily="monospace">working node</text>
|
|
492
|
+
<circle cx="18" cy="52" r="6" fill={isLight ? '#0d9488' : '#2dd4bf'} />
|
|
493
|
+
<text x="34" y="56" fill={pal.legendText} fontSize="11" fontFamily="monospace">online idle</text>
|
|
494
|
+
<circle cx="18" cy="78" r="6" fill={isLight ? '#94a3b8' : '#6b7280'} />
|
|
495
|
+
<text x="34" y="82" fill={pal.legendText} fontSize="11" fontFamily="monospace">offline / no SSE</text>
|
|
496
|
+
<path d="M150,78 Q176,52 210,78" fill="none" stroke={pal.flowEdge} strokeWidth="3" markerEnd="url(#topo-arrow)" />
|
|
408
497
|
</g>
|
|
409
498
|
</svg>
|
|
410
499
|
</div>
|
package/app/globals.css
CHANGED
|
@@ -517,6 +517,41 @@ body {
|
|
|
517
517
|
box-shadow: 0 6px 18px -10px rgba(15, 23, 42, 0.12);
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
521
|
+
Login surface (round 1 polish)
|
|
522
|
+
Restrained, minimal — first impression for new Agent Network users.
|
|
523
|
+
─────────────────────────────────────────────────────────────────── */
|
|
524
|
+
.anet-login-bg {
|
|
525
|
+
background:
|
|
526
|
+
radial-gradient(circle at 50% 30%, rgba(34, 211, 238, 0.05), transparent 55%),
|
|
527
|
+
radial-gradient(circle at 33% 75%, rgba(59, 130, 246, 0.045), transparent 55%);
|
|
528
|
+
}
|
|
529
|
+
[data-theme="light"] .anet-login-bg,
|
|
530
|
+
[data-theme="mint"] .anet-login-bg {
|
|
531
|
+
background-image:
|
|
532
|
+
radial-gradient(circle at 50% 35%, rgba(0, 158, 126, 0.06), transparent 60%),
|
|
533
|
+
radial-gradient(circle, rgba(15, 23, 42, 0.05) 1px, transparent 1px);
|
|
534
|
+
background-size: 100% 100%, 24px 24px;
|
|
535
|
+
}
|
|
536
|
+
[data-theme="light"] .anet-login-mark,
|
|
537
|
+
[data-theme="mint"] .anet-login-mark {
|
|
538
|
+
background: var(--bg-secondary) !important;
|
|
539
|
+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 0 0 1px var(--border);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* Sidebar brand "online" pulse — slow, restrained, NO glow.
|
|
543
|
+
1.6s opacity drift between 1.0 and 0.5; nothing scales, nothing blurs. */
|
|
544
|
+
@keyframes anet-brand-pulse-kf {
|
|
545
|
+
0%, 100% { opacity: 1; }
|
|
546
|
+
50% { opacity: 0.4; }
|
|
547
|
+
}
|
|
548
|
+
.anet-brand-pulse {
|
|
549
|
+
animation: anet-brand-pulse-kf 1.6s ease-in-out infinite;
|
|
550
|
+
}
|
|
551
|
+
@media (prefers-reduced-motion: reduce) {
|
|
552
|
+
.anet-brand-pulse { animation: none; }
|
|
553
|
+
}
|
|
554
|
+
|
|
520
555
|
/* P0-3 baseline: error / warning chip colors for light themes.
|
|
521
556
|
The component-level copy (replacing raw JSON with human text) is fixed
|
|
522
557
|
in the Logs / Server-Logs page components separately. */
|
package/app/login/page.tsx
CHANGED
|
@@ -44,21 +44,32 @@ export default function LoginPage() {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
|
-
<main className="min-h-screen bg-[#0a0a1a] text-gray-100 font-mono flex items-center justify-center relative overflow-hidden">
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
<main className="min-h-screen bg-[#0a0a1a] text-gray-100 font-mono flex items-center justify-center relative overflow-hidden px-4 py-10 sm:py-16">
|
|
48
|
+
{/* Subtle off-grid background — restraint over AI-glow.
|
|
49
|
+
Dark themes get a faint radial wash; light themes show a low-contrast
|
|
50
|
+
dotted grid for surface texture without noise. */}
|
|
51
|
+
<div className="absolute inset-0 pointer-events-none anet-login-bg" aria-hidden />
|
|
52
52
|
|
|
53
|
-
<div className="relative w-full max-w-sm
|
|
54
|
-
<div className="text-center mb-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
<div className="relative w-full max-w-sm">
|
|
54
|
+
<div className="text-center mb-7">
|
|
55
|
+
{/* Brand mark — uses the favicon 3-node mesh SVG which IS the
|
|
56
|
+
agent-network concept (nodes + edges). Replaces the previous
|
|
57
|
+
flat emerald square placeholder. */}
|
|
58
|
+
<div className="inline-flex items-center justify-center w-16 h-16 mb-4 rounded-2xl bg-[#111128] border border-[#2a2a4a] anet-login-mark">
|
|
59
|
+
<svg className="w-9 h-9" viewBox="0 0 32 32" aria-hidden>
|
|
60
|
+
<circle cx="16" cy="16" r="10" fill="none" stroke="currentColor" strokeWidth="1.5" opacity="0.25" className="text-cyan-400 anet-login-mark-ring" />
|
|
61
|
+
<line x1="16" y1="10" x2="10" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.5" className="text-cyan-400 anet-login-mark-edge" />
|
|
62
|
+
<line x1="16" y1="10" x2="22" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.5" className="text-cyan-400 anet-login-mark-edge" />
|
|
63
|
+
<line x1="10" y1="20" x2="22" y2="20" stroke="currentColor" strokeWidth="1" opacity="0.5" className="text-cyan-400 anet-login-mark-edge" />
|
|
64
|
+
<circle cx="16" cy="10" r="3" fill="currentColor" className="text-cyan-400 anet-login-mark-node-a" />
|
|
65
|
+
<circle cx="10" cy="20" r="3" fill="currentColor" className="text-green-400 anet-login-mark-node-b" />
|
|
66
|
+
<circle cx="22" cy="20" r="3" fill="currentColor" className="text-violet-400 anet-login-mark-node-c" />
|
|
58
67
|
</svg>
|
|
59
68
|
</div>
|
|
60
|
-
<h1 className="text-2xl font-
|
|
61
|
-
<p className="text-
|
|
69
|
+
<h1 className="text-[22px] sm:text-2xl font-semibold text-white tracking-tight">Agent Network</h1>
|
|
70
|
+
<p className="text-[13px] text-gray-500 mt-1.5 leading-snug">
|
|
71
|
+
One control plane for your AI agents · <span className="text-gray-600">Tasks · Mesh · Messages</span>
|
|
72
|
+
</p>
|
|
62
73
|
</div>
|
|
63
74
|
|
|
64
75
|
{/* Mode toggle */}
|
|
@@ -96,7 +107,9 @@ export default function LoginPage() {
|
|
|
96
107
|
</button>
|
|
97
108
|
</form>
|
|
98
109
|
|
|
99
|
-
<p className="text-center text-
|
|
110
|
+
<p className="text-center text-[11px] text-gray-700 mt-6">
|
|
111
|
+
Sleep2AGI · Apache-2.0 · <a href="https://anet.sh" className="hover:text-gray-500 transition-colors">anet.sh</a>
|
|
112
|
+
</p>
|
|
100
113
|
</div>
|
|
101
114
|
</main>
|
|
102
115
|
);
|
package/app/logs/page.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
|
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
import { timeAgo } from '../components/utils';
|
|
6
6
|
import { useNetworkId } from '../lib/network-context';
|
|
7
|
+
import { EmptyState } from '../components/EmptyState';
|
|
7
8
|
|
|
8
9
|
interface AuditLog {
|
|
9
10
|
id: string;
|
|
@@ -121,11 +122,7 @@ export default function LogsPage() {
|
|
|
121
122
|
{[1,2,3,4,5].map(i => <div key={i} className="h-14 bg-gray-800/20 rounded-lg" />)}
|
|
122
123
|
</div>
|
|
123
124
|
) : logs.length === 0 ? (
|
|
124
|
-
<
|
|
125
|
-
<div className="text-gray-600 text-4xl mb-4">--</div>
|
|
126
|
-
<h3 className="text-gray-400 text-lg font-medium mb-2">No audit logs</h3>
|
|
127
|
-
<p className="text-gray-600 text-sm">Events will appear here when users register, login, or perform actions.</p>
|
|
128
|
-
</div>
|
|
125
|
+
<EmptyState variant="logs" />
|
|
129
126
|
) : (
|
|
130
127
|
<div className="space-y-2">
|
|
131
128
|
{logs.map(log => (
|
package/app/messages/page.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useMemo, useState } from 'react';
|
|
4
4
|
import { useMessages } from '../lib/hooks';
|
|
5
5
|
import { timeAgo } from '../components/utils';
|
|
6
|
+
import { EmptyState } from '../components/EmptyState';
|
|
6
7
|
|
|
7
8
|
interface MessageItem {
|
|
8
9
|
id: string;
|
|
@@ -186,11 +187,7 @@ export default function MessagesPage() {
|
|
|
186
187
|
{[1, 2, 3, 4, 5].map(i => <div key={i} className="h-20 rounded-2xl bg-gray-800/20" />)}
|
|
187
188
|
</div>
|
|
188
189
|
) : filtered.length === 0 ? (
|
|
189
|
-
<
|
|
190
|
-
<div className="text-gray-600 text-4xl mb-4">--</div>
|
|
191
|
-
<h3 className="text-gray-400 text-lg font-medium mb-2">No messages</h3>
|
|
192
|
-
<p className="text-gray-600 text-sm">Messages between agents will appear here.</p>
|
|
193
|
-
</div>
|
|
190
|
+
<EmptyState variant="messages" />
|
|
194
191
|
) : viewMode === 'grouped' ? (
|
|
195
192
|
<div className="space-y-4">
|
|
196
193
|
{conversations.map((conv, ci) => (
|
package/app/nodes/page.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
|
|
5
5
|
import { timeAgo } from '../components/utils';
|
|
6
6
|
import { useSessions, useHealth } from '../lib/hooks';
|
|
7
7
|
import { TaskChatPanel } from '../components/TaskChatPanel';
|
|
8
|
+
import { EmptyState } from '../components/EmptyState';
|
|
8
9
|
import type { Session } from '../components/types';
|
|
9
10
|
|
|
10
11
|
const STATUS_COLORS: Record<string, string> = {
|
|
@@ -163,7 +164,11 @@ export default function NodesPage() {
|
|
|
163
164
|
{[1,2,3,4].map(i => <div key={i} className="h-16 bg-gray-800/20 rounded-lg" />)}
|
|
164
165
|
</div>
|
|
165
166
|
) : filtered.length === 0 ? (
|
|
166
|
-
<
|
|
167
|
+
<EmptyState
|
|
168
|
+
variant="nodes"
|
|
169
|
+
title="No nodes match your filters"
|
|
170
|
+
sub="Try clearing search or status filters, or wait for an agent to register."
|
|
171
|
+
/>
|
|
167
172
|
) : viewMode === 'grid' ? (
|
|
168
173
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
|
169
174
|
{filtered.map(s => {
|