@sleep2agi/agent-network-dashboard 0.4.5-preview.5 → 0.4.5-preview.50

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.
Files changed (268) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +50 -50
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +4 -4
  15. package/.next/server/app/_not-found.rsc +12 -12
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/admin/page.js.nft.json +1 -1
  23. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  24. package/.next/server/app/admin.html +4 -4
  25. package/.next/server/app/admin.rsc +14 -14
  26. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  27. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  28. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  29. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  30. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  31. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  32. package/.next/server/app/index.html +4 -4
  33. package/.next/server/app/index.rsc +14 -14
  34. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  35. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  36. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  37. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  38. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  39. package/.next/server/app/login/page.js.nft.json +1 -1
  40. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  41. package/.next/server/app/login.html +2 -2
  42. package/.next/server/app/login.rsc +14 -14
  43. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  44. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  45. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  46. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  48. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  49. package/.next/server/app/logs/page.js.nft.json +1 -1
  50. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  51. package/.next/server/app/logs.html +4 -4
  52. package/.next/server/app/logs.rsc +14 -14
  53. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  54. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  55. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  56. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  57. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  58. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  59. package/.next/server/app/messages/page.js.nft.json +1 -1
  60. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  61. package/.next/server/app/messages.html +4 -4
  62. package/.next/server/app/messages.rsc +14 -14
  63. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  64. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  65. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  66. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  67. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  68. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  69. package/.next/server/app/node/page.js.nft.json +1 -1
  70. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  71. package/.next/server/app/node.html +4 -4
  72. package/.next/server/app/node.rsc +14 -14
  73. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  74. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  75. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  76. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  77. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  78. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  79. package/.next/server/app/nodes/page.js.nft.json +1 -1
  80. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  81. package/.next/server/app/nodes.html +4 -4
  82. package/.next/server/app/nodes.rsc +14 -14
  83. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  84. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  85. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  86. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  87. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  88. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  89. package/.next/server/app/page.js.nft.json +1 -1
  90. package/.next/server/app/page_client-reference-manifest.js +1 -1
  91. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  92. package/.next/server/app/server-logs.html +4 -4
  93. package/.next/server/app/server-logs.rsc +14 -14
  94. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  95. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  96. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  97. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  98. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  99. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  100. package/.next/server/app/settings/networks/page.js.nft.json +1 -1
  101. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  102. package/.next/server/app/settings/networks.html +4 -4
  103. package/.next/server/app/settings/networks.rsc +14 -14
  104. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  105. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  106. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  107. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  108. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  109. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  110. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  111. package/.next/server/app/settings/page.js.nft.json +1 -1
  112. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  113. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  114. package/.next/server/app/settings/tokens.html +4 -4
  115. package/.next/server/app/settings/tokens.rsc +14 -14
  116. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  122. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/settings.html +4 -4
  124. package/.next/server/app/settings.rsc +14 -14
  125. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  131. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  132. package/.next/server/app/tasks/page.js.nft.json +1 -1
  133. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  134. package/.next/server/app/tasks.html +4 -4
  135. package/.next/server/app/tasks.rsc +14 -14
  136. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  137. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  138. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  139. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  140. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  141. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  142. package/.next/server/chunks/ssr/[root-of-the-server]__02ee4km._.js +3 -0
  143. package/.next/server/chunks/ssr/[root-of-the-server]__02ee4km._.js.map +1 -0
  144. package/.next/server/chunks/ssr/[root-of-the-server]__0n4l2is._.js +3 -0
  145. package/.next/server/chunks/ssr/[root-of-the-server]__0n4l2is._.js.map +1 -0
  146. package/.next/server/chunks/ssr/[root-of-the-server]__0o8yj3c._.js +3 -0
  147. package/.next/server/chunks/ssr/[root-of-the-server]__0o8yj3c._.js.map +1 -0
  148. package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js +1 -1
  149. package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js.map +1 -1
  150. package/.next/server/chunks/ssr/_0nne7mz._.js +3 -0
  151. package/.next/server/chunks/ssr/_0nne7mz._.js.map +1 -0
  152. package/.next/server/chunks/ssr/app_0.zbtn5._.js +9 -0
  153. package/.next/server/chunks/ssr/app_0.zbtn5._.js.map +1 -0
  154. package/.next/server/chunks/ssr/app_04n78pr._.js +3 -0
  155. package/.next/server/chunks/ssr/app_04n78pr._.js.map +1 -0
  156. package/.next/server/chunks/ssr/app_0_agk0r._.js +3 -0
  157. package/.next/server/chunks/ssr/app_0_agk0r._.js.map +1 -0
  158. package/.next/server/chunks/ssr/app_0aum_g5._.js +3 -0
  159. package/.next/server/chunks/ssr/app_0aum_g5._.js.map +1 -0
  160. package/.next/server/chunks/ssr/app_0esuljp._.js +3 -0
  161. package/.next/server/chunks/ssr/app_0esuljp._.js.map +1 -0
  162. package/.next/server/chunks/ssr/app_0izi28j._.js +1 -1
  163. package/.next/server/chunks/ssr/app_0izi28j._.js.map +1 -1
  164. package/.next/server/chunks/ssr/app_0py1sns._.js +3 -0
  165. package/.next/server/chunks/ssr/app_0py1sns._.js.map +1 -0
  166. package/.next/server/chunks/ssr/app_0tncr1_._.js +3 -0
  167. package/.next/server/chunks/ssr/app_0tncr1_._.js.map +1 -0
  168. package/.next/server/chunks/ssr/app_components_08ys5y3._.js +5 -5
  169. package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +1 -1
  170. package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js +1 -1
  171. package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js.map +1 -1
  172. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +1 -1
  173. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +1 -1
  174. package/.next/server/middleware-build-manifest.js +3 -3
  175. package/.next/server/pages/404.html +4 -4
  176. package/.next/server/pages/500.html +1 -1
  177. package/.next/server/server-reference-manifest.js +1 -1
  178. package/.next/server/server-reference-manifest.json +1 -1
  179. package/.next/static/chunks/000qcyr-yo73c.css +2 -0
  180. package/.next/static/chunks/02oje7pf-g1v5.js +1 -0
  181. package/.next/static/chunks/06x7w8nwggx~~.js +1 -0
  182. package/.next/static/chunks/{07okhs5561y5y.js → 0c1bp~g7.8ru..js} +1 -1
  183. package/.next/static/chunks/0edt_z0rlxgfk.js +1 -0
  184. package/.next/static/chunks/0irkayokwc1fm.js +1 -0
  185. package/.next/static/chunks/0j8ee64jv6zb7.js +7 -0
  186. package/.next/static/chunks/0l2rkodu2.v~r.js +1 -0
  187. package/.next/static/chunks/0mdu-2u9-dx.4.js +1 -0
  188. package/.next/static/chunks/0uar..avu998d.js +1 -0
  189. package/.next/static/chunks/0vqk_f6kf2hnl.js +1 -0
  190. package/.next/static/chunks/0xu37uygp5fs-.js +1 -0
  191. package/.next/static/chunks/0~n.cadw9_e8s.js +7 -0
  192. package/.next/static/chunks/12hyhrnzrx~ys.js +1 -0
  193. package/.next/static/chunks/1528e.3d.l1ve.js +1 -0
  194. package/.next/static/chunks/15c9pv6gxir.5.js +1 -0
  195. package/.next/trace +2 -2
  196. package/.next/trace-build +1 -1
  197. package/app/admin/page.tsx +77 -25
  198. package/app/components/AgentCard.tsx +19 -5
  199. package/app/components/AliasAvatar.tsx +51 -0
  200. package/app/components/BroadcastBar.tsx +22 -4
  201. package/app/components/DispatchPanel.tsx +9 -5
  202. package/app/components/EmptyState.tsx +77 -4
  203. package/app/components/HealthBanner.tsx +1 -1
  204. package/app/components/HelpOverlay.tsx +50 -10
  205. package/app/components/InboxPanel.tsx +6 -3
  206. package/app/components/Sidebar.tsx +49 -41
  207. package/app/components/TaskChatPanel.tsx +8 -10
  208. package/app/components/TaskDrawer.tsx +72 -28
  209. package/app/components/TopoGraph.tsx +118 -24
  210. package/app/globals.css +90 -0
  211. package/app/lib/status.ts +63 -0
  212. package/app/lib/version.ts +7 -0
  213. package/app/login/page.tsx +40 -7
  214. package/app/logs/page.tsx +72 -21
  215. package/app/messages/page.tsx +8 -33
  216. package/app/node/page.tsx +48 -25
  217. package/app/nodes/page.tsx +21 -23
  218. package/app/page.tsx +77 -17
  219. package/app/server-logs/page.tsx +70 -12
  220. package/app/settings/networks/page.tsx +37 -13
  221. package/app/settings/page.tsx +38 -10
  222. package/app/tasks/page.tsx +90 -93
  223. package/package.json +1 -1
  224. package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js +0 -3
  225. package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js.map +0 -1
  226. package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js +0 -3
  227. package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js.map +0 -1
  228. package/.next/server/chunks/ssr/[root-of-the-server]__12mpoig._.js +0 -3
  229. package/.next/server/chunks/ssr/[root-of-the-server]__12mpoig._.js.map +0 -1
  230. package/.next/server/chunks/ssr/_0d-fuox._.js +0 -3
  231. package/.next/server/chunks/ssr/_0d-fuox._.js.map +0 -1
  232. package/.next/server/chunks/ssr/app_0.0wpa~._.js +0 -3
  233. package/.next/server/chunks/ssr/app_0.0wpa~._.js.map +0 -1
  234. package/.next/server/chunks/ssr/app_0ap_oiq._.js +0 -3
  235. package/.next/server/chunks/ssr/app_0ap_oiq._.js.map +0 -1
  236. package/.next/server/chunks/ssr/app_0dk5wao._.js +0 -3
  237. package/.next/server/chunks/ssr/app_0dk5wao._.js.map +0 -1
  238. package/.next/server/chunks/ssr/app_0et5yua._.js +0 -3
  239. package/.next/server/chunks/ssr/app_0et5yua._.js.map +0 -1
  240. package/.next/server/chunks/ssr/app_0ff~kjj._.js +0 -9
  241. package/.next/server/chunks/ssr/app_0ff~kjj._.js.map +0 -1
  242. package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +0 -3
  243. package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +0 -1
  244. package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js +0 -3
  245. package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js.map +0 -1
  246. package/.next/static/chunks/02flrewxlf_m_.js +0 -1
  247. package/.next/static/chunks/051gslfjka1kj.js +0 -1
  248. package/.next/static/chunks/06t_vzei8s9_h.js +0 -1
  249. package/.next/static/chunks/07oobuohrpigy.js +0 -1
  250. package/.next/static/chunks/0e7c40p~wq665.js +0 -1
  251. package/.next/static/chunks/0g7m7zk.v7g6h.js +0 -1
  252. package/.next/static/chunks/0khpyxpdrp~ty.js +0 -1
  253. package/.next/static/chunks/0m328uz90rp81.js +0 -1
  254. package/.next/static/chunks/0ogeals5ufhho.js +0 -1
  255. package/.next/static/chunks/0svy-tmgb8ajq.js +0 -7
  256. package/.next/static/chunks/1031v6nw33ud3.js +0 -1
  257. package/.next/static/chunks/10ir.g.n~hre0.js +0 -7
  258. package/.next/static/chunks/10njj7kn9ylkf.js +0 -1
  259. package/.next/static/chunks/13.dst7liziwo.js +0 -1
  260. package/.next/static/chunks/168ijr_r0ku-q.css +0 -2
  261. package/public/file.svg +0 -1
  262. package/public/globe.svg +0 -1
  263. package/public/next.svg +0 -1
  264. package/public/vercel.svg +0 -1
  265. package/public/window.svg +0 -1
  266. /package/.next/static/{HWCPpUEba8WqttkPXD5x8 → AQXM9jmgIyScbdEjjvrFL}/_buildManifest.js +0 -0
  267. /package/.next/static/{HWCPpUEba8WqttkPXD5x8 → AQXM9jmgIyScbdEjjvrFL}/_clientMiddlewareManifest.js +0 -0
  268. /package/.next/static/{HWCPpUEba8WqttkPXD5x8 → AQXM9jmgIyScbdEjjvrFL}/_ssgManifest.js +0 -0
@@ -23,6 +23,30 @@ const LEVEL_BADGE: Record<LogLine['level'], string> = {
23
23
  error: 'bg-red-500/10 text-red-300 border-red-500/30',
24
24
  };
25
25
 
26
+ /** Left-rail strip color per log level — gives the eye a vertical guide
27
+ * when scanning long log walls. Plain hex so Tailwind purge can't drop it. */
28
+ const LEVEL_STRIPE: Record<LogLine['level'], string> = {
29
+ log: 'transparent',
30
+ info: '#22d3ee',
31
+ warn: '#f59e0b',
32
+ error: '#ef4444',
33
+ };
34
+
35
+ /** Highlight search-matched substring inside a log line. Case-insensitive
36
+ * first-match wrap; mark inherits color so log-level coloring stays. */
37
+ function highlightSearch(line: string, q: string) {
38
+ if (!q) return line;
39
+ const idx = line.toLowerCase().indexOf(q.toLowerCase());
40
+ if (idx < 0) return line;
41
+ return (
42
+ <>
43
+ {line.slice(0, idx)}
44
+ <mark className="bg-yellow-500/30 text-yellow-100 rounded-sm px-0.5">{line.slice(idx, idx + q.length)}</mark>
45
+ {line.slice(idx + q.length)}
46
+ </>
47
+ );
48
+ }
49
+
26
50
  function shortTime(iso: string) {
27
51
  try {
28
52
  const d = new Date(iso);
@@ -93,24 +117,48 @@ export default function ServerLogsPage() {
93
117
 
94
118
  return (
95
119
  <div className="p-6 max-w-[1600px] mx-auto">
96
- <div className="flex items-baseline justify-between mb-4">
97
- <div className="flex items-baseline gap-3">
120
+ <div className="flex items-center justify-between mb-4 gap-3">
121
+ <div className="flex items-center gap-3 min-w-0">
98
122
  <h1 className="text-2xl font-bold text-white">Server Logs</h1>
99
- <span className="text-xs text-gray-500">CommHub stdout/stderr · 最新在上</span>
123
+ {logs.length > 0 && (
124
+ <span className="text-xs bg-blue-900/30 text-blue-400 px-2 py-0.5 rounded-full border border-blue-800/30 tabular-nums">
125
+ {logs.length}
126
+ </span>
127
+ )}
128
+ <span className="text-xs text-gray-500 hidden sm:inline">CommHub stdout/stderr</span>
100
129
  </div>
101
- <div className="flex items-center gap-2 text-xs">
130
+ <div className="flex items-center gap-2 text-xs shrink-0">
102
131
  <button
103
132
  onClick={() => setPaused(p => !p)}
104
- className={`px-3 py-1 rounded border ${paused ? 'bg-amber-500/10 text-amber-300 border-amber-500/30' : 'bg-cyan-500/10 text-cyan-300 border-cyan-500/30'}`}
133
+ className={`flex items-center gap-1.5 px-3 py-1 rounded border ${paused ? 'bg-amber-500/10 text-amber-300 border-amber-500/30' : 'bg-cyan-500/10 text-cyan-300 border-cyan-500/30'}`}
105
134
  >
106
- {paused ? '▶ Resume' : '⏸ Pause'}
135
+ {paused ? (
136
+ <>
137
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" aria-hidden><path d="M8 5v14l11-7z" /></svg>
138
+ Resume
139
+ </>
140
+ ) : (
141
+ <>
142
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
143
+ <rect x="6" y="5" width="4" height="14" rx="0.5" />
144
+ <rect x="14" y="5" width="4" height="14" rx="0.5" />
145
+ </svg>
146
+ Pause
147
+ </>
148
+ )}
107
149
  </button>
108
150
  <label className="flex items-center gap-1 text-gray-400 select-none">
109
151
  <input type="checkbox" checked={autoRefresh} onChange={e => setAutoRefresh(e.target.checked)} className="accent-cyan-500" />
110
- auto-refresh
152
+ <span className="hidden sm:inline">auto-refresh</span>
111
153
  </label>
112
- <button onClick={() => fetchLogs(true)} className="px-3 py-1 rounded border bg-gray-500/10 text-gray-300 border-gray-500/20 hover:bg-gray-500/20">
113
- ↻ Reload
154
+ <button onClick={() => fetchLogs(true)} className="flex items-center gap-1.5 px-3 py-1 rounded border bg-gray-500/10 text-gray-300 border-gray-500/20 hover:bg-gray-500/20">
155
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
156
+ <path d="M3 12a9 9 0 0 1 15.5-6.4L21 8" />
157
+ <path d="M21 3v5h-5" />
158
+ <path d="M21 12a9 9 0 0 1-15.5 6.4L3 16" />
159
+ <path d="M3 21v-5h5" />
160
+ </svg>
161
+ <span className="hidden sm:inline">Reload</span>
114
162
  </button>
115
163
  </div>
116
164
  </div>
@@ -182,12 +230,22 @@ export default function ServerLogsPage() {
182
230
  ) : (
183
231
  <div className="divide-y divide-[#0d0d18] max-h-[calc(100vh-260px)] overflow-y-auto">
184
232
  {filtered.map((l, i) => (
185
- <div key={`${l.ts}-${i}`} className="px-3 py-1 hover:bg-[#0a0a14] flex gap-3">
186
- <span className="text-gray-600 shrink-0 w-[110px] text-[10px]">{shortTime(l.ts)}</span>
233
+ <div key={`${l.ts}-${i}`} className="relative pl-3 pr-3 py-1 hover:bg-[#0a0a14] flex gap-3">
234
+ {/* Round 32: 2px left rail keyed to level. Makes warn/error
235
+ spike visible in a wall of `log` lines without users having
236
+ to read the level chip on every row. */}
237
+ <span
238
+ aria-hidden
239
+ className="absolute left-0 top-0 bottom-0 w-0.5"
240
+ style={{ backgroundColor: LEVEL_STRIPE[l.level] }}
241
+ />
242
+ <span className="text-gray-600 shrink-0 w-[100px] text-[10px] tabular-nums">{shortTime(l.ts)}</span>
187
243
  <span className={`shrink-0 px-1.5 rounded border text-[9px] uppercase ${LEVEL_BADGE[l.level]}`}>
188
244
  {l.level}
189
245
  </span>
190
- <span className={`break-all whitespace-pre-wrap ${LEVEL_COLOR[l.level]}`}>{l.line}</span>
246
+ <span className={`break-all whitespace-pre-wrap ${LEVEL_COLOR[l.level]}`}>
247
+ {highlightSearch(l.line, search)}
248
+ </span>
191
249
  </div>
192
250
  ))}
193
251
  </div>
@@ -4,6 +4,7 @@ import { useEffect, useState, useCallback } from 'react';
4
4
  import Link from 'next/link';
5
5
  import { timeAgo } from '../../components/utils';
6
6
  import { EmptyState } from '../../components/EmptyState';
7
+ import { AliasAvatar } from '../../components/AliasAvatar';
7
8
  import useSWR from 'swr';
8
9
 
9
10
  const fetcher = (url: string) => fetch(url).then(r => r.json());
@@ -123,7 +124,12 @@ export default function NetworksPage() {
123
124
 
124
125
  {/* List */}
125
126
  <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
126
- <h2 className="text-sm font-semibold text-gray-300 mb-3">My Networks</h2>
127
+ <div className="flex items-center justify-between mb-3">
128
+ <h2 className="text-sm font-semibold text-gray-300">My Networks</h2>
129
+ {networks.length > 0 && (
130
+ <span className="text-[10px] text-gray-600 uppercase tracking-wide">{networks.length} total</span>
131
+ )}
132
+ </div>
127
133
  {loading ? (
128
134
  <div className="animate-pulse space-y-2">{[1,2].map(i => <div key={i} className="h-14 bg-gray-800/20 rounded" />)}</div>
129
135
  ) : networks.length === 0 ? (
@@ -136,11 +142,14 @@ export default function NetworksPage() {
136
142
  <div className="space-y-3">
137
143
  {networks.map(n => (
138
144
  <div key={n.network_id} className="bg-[#0a0a15] rounded-lg px-4 py-3 border border-[#1a1a2a]">
139
- <div className="flex items-center justify-between">
140
- <div className="min-w-0">
141
- <div className="text-sm text-white font-medium">{n.network_name}</div>
142
- <div className="text-[10px] text-gray-500">{n.network_id} · {timeAgo(n.created_at)}</div>
143
- {n.description && <div className="text-xs text-gray-400 mt-1">{n.description}</div>}
145
+ <div className="flex items-start justify-between gap-3">
146
+ <div className="flex items-start gap-3 min-w-0">
147
+ <AliasAvatar alias={n.network_name} size={28} />
148
+ <div className="min-w-0">
149
+ <div className="text-sm text-white font-medium truncate">{n.network_name}</div>
150
+ <div className="text-[10px] text-gray-500 font-mono truncate" title={n.network_id}>{n.network_id} · {timeAgo(n.created_at)}</div>
151
+ {n.description && <div className="text-xs text-gray-400 mt-1">{n.description}</div>}
152
+ </div>
144
153
  </div>
145
154
  <div className="flex gap-2 shrink-0">
146
155
  <button onClick={() => {
@@ -169,14 +178,20 @@ export default function NetworksPage() {
169
178
  {(members[n.network_id] || []).length === 0 ? (
170
179
  <div className="text-[10px] text-gray-600">No members or V3 auth required</div>
171
180
  ) : (
172
- (members[n.network_id] || []).map(m => (
173
- <div key={m.user_id} className="flex items-center justify-between text-xs py-1">
174
- <span className="text-gray-300">{m.username || m.display_name}</span>
175
- <span className={`px-1.5 py-0.5 rounded border text-[10px] ${
181
+ (members[n.network_id] || []).map(m => {
182
+ const name = m.username || m.display_name || '?';
183
+ return (
184
+ <div key={m.user_id} className="flex items-center gap-2 justify-between text-xs py-1">
185
+ <div className="flex items-center gap-2 min-w-0">
186
+ <AliasAvatar alias={name} size={16} />
187
+ <span className="text-gray-300 truncate">{name}</span>
188
+ </div>
189
+ <span className={`px-1.5 py-0.5 rounded border text-[10px] shrink-0 ${
176
190
  m.role === 'owner' ? 'text-yellow-300 border-yellow-500/20' : 'text-gray-400 border-gray-600/20'
177
191
  }`}>{m.role}</span>
178
192
  </div>
179
- ))
193
+ );
194
+ })
180
195
  )}
181
196
 
182
197
  {/* Invite */}
@@ -196,8 +211,17 @@ export default function NetworksPage() {
196
211
  </div>
197
212
  {inviteCode && (
198
213
  <div className="mt-2 bg-green-500/5 border border-green-500/20 rounded px-3 py-2">
199
- <div className="text-[10px] text-green-400 mb-1">Invite code:</div>
200
- <code className="text-xs text-green-300 select-all">{inviteCode}</code>
214
+ <div className="flex items-center justify-between mb-1">
215
+ <div className="text-[10px] text-green-400 uppercase tracking-wide">Invite code</div>
216
+ <button
217
+ type="button"
218
+ onClick={() => { navigator.clipboard?.writeText(inviteCode).catch(() => {}); }}
219
+ className="text-[10px] text-green-300 hover:text-green-200 px-1.5 py-0.5 rounded border border-green-500/20 hover:bg-green-500/10"
220
+ >
221
+ Copy
222
+ </button>
223
+ </div>
224
+ <code className="text-xs text-green-300 select-all break-all">{inviteCode}</code>
201
225
  </div>
202
226
  )}
203
227
  </div>
@@ -3,6 +3,7 @@
3
3
  import { useState } from 'react';
4
4
  import Link from 'next/link';
5
5
  import { useAnetConfig, useHealth, useLicense } from '../lib/hooks';
6
+ import { DASHBOARD_VERSION } from '../lib/version';
6
7
 
7
8
  export default function SettingsPage() {
8
9
  const { config } = useAnetConfig();
@@ -18,12 +19,31 @@ export default function SettingsPage() {
18
19
 
19
20
  return (
20
21
  <div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
21
- <h1 className="text-2xl font-bold text-white mb-6 lg:ml-0 ml-10">Settings</h1>
22
+ <h1 className="text-2xl font-bold text-white mb-3 lg:ml-0 ml-10">Settings</h1>
22
23
 
23
- <div className="max-w-2xl space-y-8">
24
+ {/* Section anchor nav (round 28) — jump to a group instead of scrolling
25
+ through the whole page. Mirrors the section headers below; pages
26
+ with 7+ cards benefit from a visible table-of-contents. */}
27
+ <nav className="mb-8 flex flex-wrap gap-1 text-xs">
28
+ {[
29
+ { href: '#connection', label: 'Connection' },
30
+ { href: '#account', label: 'Account' },
31
+ { href: '#resources', label: 'Resources' },
32
+ ].map(a => (
33
+ <a key={a.href} href={a.href}
34
+ className="rounded-md px-2.5 py-1 text-gray-500 hover:text-cyan-300 hover:bg-cyan-500/10 transition-colors">
35
+ {a.label}
36
+ </a>
37
+ ))}
38
+ </nav>
39
+
40
+ <div className="max-w-2xl space-y-10">
24
41
  {/* ── Group: Connection ─────────────────────────────────── */}
25
- <div className="space-y-4">
26
- <div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 px-1">Connection</div>
42
+ <div id="connection" className="space-y-4 scroll-mt-6">
43
+ <div className="flex items-center gap-2 px-1">
44
+ <div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Connection</div>
45
+ <div className="flex-1 h-px bg-[#2a2a4a]" />
46
+ </div>
27
47
 
28
48
  {/* CommHub Connection */}
29
49
  <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
@@ -88,7 +108,9 @@ export default function SettingsPage() {
88
108
  <div className="space-y-3 text-sm">
89
109
  <div className={rowClass}>
90
110
  <span className="text-gray-500">Version</span>
91
- <span className={`text-gray-300 ${valueClass}`}>V2.1</span>
111
+ <span className={`text-gray-300 font-mono ${valueClass}`} title="From package.json @sleep2agi/agent-network-dashboard">
112
+ {DASHBOARD_VERSION}
113
+ </span>
92
114
  </div>
93
115
  <div className={rowClass}>
94
116
  <span className="text-gray-500">Data Layer</span>
@@ -96,15 +118,18 @@ export default function SettingsPage() {
96
118
  </div>
97
119
  <div className={rowClass}>
98
120
  <span className="text-gray-500">Pages</span>
99
- <span className={`text-gray-300 ${valueClass}`}>Overview, Tasks, Nodes, Messages, Settings</span>
121
+ <span className={`text-gray-300 ${valueClass}`}>Overview, Tasks, Nodes, Messages, Networks, Audit, Server Logs, Admin, Settings</span>
100
122
  </div>
101
123
  </div>
102
124
  </section>
103
125
  </div>
104
126
 
105
127
  {/* ── Group: Account ────────────────────────────────────── */}
106
- <div className="space-y-4">
107
- <div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 px-1">Account</div>
128
+ <div id="account" className="space-y-4 scroll-mt-6">
129
+ <div className="flex items-center gap-2 px-1">
130
+ <div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Account</div>
131
+ <div className="flex-1 h-px bg-[#2a2a4a]" />
132
+ </div>
108
133
 
109
134
  {/* License */}
110
135
  <section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
@@ -232,8 +257,11 @@ export default function SettingsPage() {
232
257
  </div>
233
258
 
234
259
  {/* ── Group: Resources ──────────────────────────────────── */}
235
- <div className="space-y-4">
236
- <div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 px-1">Resources</div>
260
+ <div id="resources" className="space-y-4 scroll-mt-6">
261
+ <div className="flex items-center gap-2 px-1">
262
+ <div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Resources</div>
263
+ <div className="flex-1 h-px bg-[#2a2a4a]" />
264
+ </div>
237
265
 
238
266
  {/* API Tokens + Networks */}
239
267
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
@@ -6,6 +6,8 @@ import Link from 'next/link';
6
6
  import { useNetworkId } from '../lib/network-context';
7
7
  import { TaskDrawer } from '../components/TaskDrawer';
8
8
  import { EmptyState } from '../components/EmptyState';
9
+ import { AliasAvatar } from '../components/AliasAvatar';
10
+ import { TASK_STATUSES, STATUS_CHIP_CLASS, STATUS_DOT_HEX, STATUS_BAR_CLASS } from '../lib/status';
9
11
 
10
12
  interface Task {
11
13
  task_id: string;
@@ -23,48 +25,8 @@ interface Task {
23
25
  expires_at: string;
24
26
  }
25
27
 
26
- const STATUS_OPTIONS = [
27
- '',
28
- 'created',
29
- 'delivered',
30
- 'acked',
31
- 'running',
32
- 'replied',
33
- 'closed',
34
- 'failed',
35
- 'cancelled',
36
- 'expired',
37
- ];
38
-
39
- const STATUS_COLORS: Record<string, string> = {
40
- created: 'bg-gray-500/10 text-gray-400 border-gray-500/20',
41
- delivered: 'bg-blue-500/10 text-blue-300 border-blue-500/20',
42
- acked: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20',
43
- running: 'bg-green-500/10 text-green-300 border-green-500/20',
44
- replied: 'bg-purple-500/10 text-purple-300 border-purple-500/20',
45
- closed: 'bg-gray-500/10 text-gray-500 border-gray-500/20',
46
- failed: 'bg-red-500/10 text-red-300 border-red-500/20',
47
- cancelled: 'bg-yellow-500/10 text-yellow-300 border-yellow-500/20',
48
- expired: 'bg-orange-500/10 text-orange-300 border-orange-500/20',
49
- };
50
-
51
- /** Small colored dot per status — visible on every tab (active + inactive)
52
- * so users can scan the row at a glance. Hex inlined to avoid Tailwind
53
- * purging dynamic `bg-${family}-400` class names. */
54
- const STATUS_DOTS: Record<string, string> = {
55
- created: '#9ca3af',
56
- delivered: '#60a5fa',
57
- acked: '#22d3ee',
58
- running: '#4ade80',
59
- replied: '#a78bfa',
60
- closed: '#6b7280',
61
- failed: '#f87171',
62
- cancelled: '#facc15',
63
- expired: '#fb923c',
64
- };
65
-
66
28
  function statusBadge(status: string) {
67
- const color = STATUS_COLORS[status] || 'bg-gray-500/10 text-gray-400 border-gray-500/20';
29
+ const color = STATUS_CHIP_CLASS[status] || 'bg-gray-500/10 text-gray-400 border-gray-500/20';
68
30
  return `text-xs px-2 py-0.5 rounded-md border ${color}`;
69
31
  }
70
32
 
@@ -158,42 +120,73 @@ function TasksContent() {
158
120
  elements) hinting more content. */}
159
121
  <div className="anet-tabstrip-wrap mb-4">
160
122
  <div className="anet-tabstrip flex sm:flex-wrap gap-1 bg-[#111128] rounded-lg border border-[#2a2a4a] p-1 overflow-x-auto sm:overflow-x-visible scrollbar-thin">
161
- {['', ...STATUS_OPTIONS.filter(Boolean)].map(s => (
162
- <button key={s} onClick={() => setFilterStatus(s)}
163
- className={`px-3 py-1.5 rounded-md text-xs transition-colors flex items-center gap-1.5 shrink-0 whitespace-nowrap ${
164
- filterStatus === s
165
- ? `${STATUS_COLORS[s] || 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'} border`
166
- : 'text-gray-500 hover:text-gray-300 hover:bg-[#1a1a2a]/40'
167
- }`}>
168
- {s && (
169
- <span
170
- aria-hidden
171
- className="inline-block w-1.5 h-1.5 rounded-full shrink-0"
172
- style={{ backgroundColor: STATUS_DOTS[s] || '#6b7280' }}
173
- />
174
- )}
175
- <span>{s || 'All'}</span>
176
- </button>
177
- ))}
123
+ {(() => {
124
+ // Round 56: compute per-status counts so the tab strip doubles as a
125
+ // status distribution dashboard. Matches Audit Log (r43) + Overview
126
+ // agent filter (r34) chip-with-count pattern.
127
+ const counts: Record<string, number> = {};
128
+ tasks.forEach(t => { counts[t.status] = (counts[t.status] || 0) + 1; });
129
+ return ['', ...TASK_STATUSES].map(s => {
130
+ const count = s === '' ? tasks.length : counts[s] || 0;
131
+ const isActive = filterStatus === s;
132
+ return (
133
+ <button
134
+ key={s}
135
+ onClick={() => setFilterStatus(s)}
136
+ disabled={count === 0 && s !== '' && !isActive}
137
+ className={`px-3 py-1.5 rounded-md text-xs transition-colors flex items-center gap-1.5 shrink-0 whitespace-nowrap disabled:opacity-30 disabled:cursor-not-allowed ${
138
+ isActive
139
+ ? `${STATUS_CHIP_CLASS[s] || 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'} border`
140
+ : 'text-gray-500 hover:text-gray-300 hover:bg-[#1a1a2a]/40'
141
+ }`}
142
+ >
143
+ {s && (
144
+ <span
145
+ aria-hidden
146
+ className="inline-block w-1.5 h-1.5 rounded-full shrink-0"
147
+ style={{ backgroundColor: STATUS_DOT_HEX[s] || '#6b7280' }}
148
+ />
149
+ )}
150
+ <span>{s || 'All'}</span>
151
+ <span className={`text-[10px] tabular-nums ${isActive ? 'opacity-80' : 'text-gray-600'}`}>{count}</span>
152
+ </button>
153
+ );
154
+ });
155
+ })()}
178
156
  </div>
179
157
  </div>
180
158
 
181
- {/* From/To Filters */}
182
- <div className="flex flex-wrap gap-3 mb-6">
183
- <input
184
- type="text"
185
- value={filterFrom}
186
- onChange={e => setFilterFrom(e.target.value)}
187
- placeholder="From node..."
188
- className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-blue-500/50 focus:outline-none w-40"
189
- />
190
- <input
191
- type="text"
192
- value={filterTo}
193
- onChange={e => setFilterTo(e.target.value)}
194
- placeholder="To node..."
195
- className="bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:border-blue-500/50 focus:outline-none w-40"
196
- />
159
+ {/* From/To Filters — same visual block as the status tabs above. */}
160
+ <div className="flex flex-wrap items-center gap-2 mb-6">
161
+ <div className="flex items-center gap-1.5 rounded-lg border border-[#2a2a4a] bg-[#111128] px-2.5 py-1.5 focus-within:border-blue-500/40">
162
+ <span className="text-[10px] uppercase tracking-wide text-gray-600">From</span>
163
+ <input
164
+ type="text"
165
+ value={filterFrom}
166
+ onChange={e => setFilterFrom(e.target.value)}
167
+ placeholder="any node"
168
+ className="w-28 bg-transparent text-sm text-white placeholder-gray-700 focus:outline-none"
169
+ />
170
+ </div>
171
+ <div className="flex items-center gap-1.5 rounded-lg border border-[#2a2a4a] bg-[#111128] px-2.5 py-1.5 focus-within:border-blue-500/40">
172
+ <span className="text-[10px] uppercase tracking-wide text-gray-600">To</span>
173
+ <input
174
+ type="text"
175
+ value={filterTo}
176
+ onChange={e => setFilterTo(e.target.value)}
177
+ placeholder="any node"
178
+ className="w-28 bg-transparent text-sm text-white placeholder-gray-700 focus:outline-none"
179
+ />
180
+ </div>
181
+ {(filterStatus || filterFrom || filterTo) && (
182
+ <button
183
+ type="button"
184
+ onClick={() => { setFilterStatus(''); setFilterFrom(''); setFilterTo(''); }}
185
+ className="rounded-lg border border-gray-700 px-2.5 py-1.5 text-[11px] text-gray-500 hover:text-gray-200 hover:border-gray-600"
186
+ >
187
+ Clear filters
188
+ </button>
189
+ )}
197
190
  </div>
198
191
 
199
192
  {/* Status distribution */}
@@ -201,21 +194,21 @@ function TasksContent() {
201
194
  const stats: Record<string, number> = {};
202
195
  tasks.forEach(t => { stats[t.status] = (stats[t.status] || 0) + 1; });
203
196
  const total = tasks.length || 1;
204
- const bars = [
205
- { key: 'running', color: 'bg-green-500', text: 'text-green-400' },
206
- { key: 'delivered', color: 'bg-blue-500', text: 'text-blue-400' },
207
- { key: 'replied', color: 'bg-purple-500', text: 'text-purple-400' },
208
- { key: 'failed', color: 'bg-red-500', text: 'text-red-400' },
209
- { key: 'expired', color: 'bg-orange-500', text: 'text-orange-400' },
210
- ].filter(b => stats[b.key]);
197
+ // Round 67: derive from shared TASK_STATUSES + STATUS_BAR_CLASS in
198
+ // app/lib/status.ts. Adding a new lifecycle status updates badge,
199
+ // tab dots, and bar in one edit.
200
+ const bars = TASK_STATUSES
201
+ .map(key => ({ key, color: STATUS_BAR_CLASS[key] }))
202
+ .filter(b => stats[b.key]);
211
203
  if (!bars.length) return null;
204
+ // Round 63: removed the per-status legend row beneath the bar —
205
+ // it duplicated the counts already shown in the tab chip strip
206
+ // since r56. The proportional bar stays because it adds a visual
207
+ // "shape of workload" that the chip numbers don't convey.
212
208
  return (
213
209
  <div className="mb-6">
214
210
  <div className="flex h-2 rounded-full overflow-hidden bg-gray-800">
215
- {bars.map(b => <div key={b.key} className={b.color} style={{ width: `${(stats[b.key]/total)*100}%` }} />)}
216
- </div>
217
- <div className="flex flex-wrap gap-3 mt-1.5 text-[10px] text-gray-500">
218
- {bars.map(b => <span key={b.key}><span className={`inline-block w-2 h-2 rounded-full ${b.color} mr-1`} />{stats[b.key]} {b.key}</span>)}
211
+ {bars.map(b => <div key={b.key} className={b.color} style={{ width: `${(stats[b.key]/total)*100}%` }} title={`${b.key}: ${stats[b.key]}`} />)}
219
212
  </div>
220
213
  </div>
221
214
  );
@@ -276,11 +269,13 @@ function TasksContent() {
276
269
  <div className="col-span-1">
277
270
  <span className={statusBadge(t.status)}>{t.status}</span>
278
271
  </div>
279
- <div className="col-span-2 text-sm text-blue-400 truncate" title={t.from_name}>
280
- {t.from_name || '--'}
272
+ <div className="col-span-2 flex items-center gap-1.5 min-w-0" title={t.from_name}>
273
+ {t.from_name && <AliasAvatar alias={t.from_name} size={18} />}
274
+ <span className="truncate text-sm text-gray-200">{t.from_name || '--'}</span>
281
275
  </div>
282
- <div className="col-span-2 text-sm text-cyan-400 truncate" title={t.to_name}>
283
- {t.to_name || '--'}
276
+ <div className="col-span-2 flex items-center gap-1.5 min-w-0" title={t.to_name}>
277
+ {t.to_name && <AliasAvatar alias={t.to_name} size={18} />}
278
+ <span className="truncate text-sm text-gray-200">{t.to_name || '--'}</span>
284
279
  </div>
285
280
  <div className="col-span-4 text-xs text-gray-400 truncate" title={t.content}>
286
281
  {t.content || '--'}
@@ -315,10 +310,12 @@ function TasksContent() {
315
310
  </svg>
316
311
  </div>
317
312
  </div>
318
- <div className="text-xs text-gray-500">
319
- <span className="text-blue-400">{t.from_name || '--'}</span>
320
- <span className="text-gray-600 mx-1">&rarr;</span>
321
- <span className="text-cyan-400">{t.to_name || '--'}</span>
313
+ <div className="flex items-center gap-1.5 text-xs text-gray-300 min-w-0">
314
+ {t.from_name && <AliasAvatar alias={t.from_name} size={16} />}
315
+ <span className="truncate max-w-[40%]">{t.from_name || '--'}</span>
316
+ <span className="text-gray-600">&rarr;</span>
317
+ {t.to_name && <AliasAvatar alias={t.to_name} size={16} />}
318
+ <span className="truncate max-w-[40%]">{t.to_name || '--'}</span>
322
319
  </div>
323
320
  <div className="text-xs text-gray-400 line-clamp-1">{t.content || '--'}</div>
324
321
  <div className="text-xs text-gray-600">{timeAgo(t.created_at)}</div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.4.5-preview.5",
3
+ "version": "0.4.5-preview.50",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -1,3 +0,0 @@
1
- module.exports=[50227,(a,b,c)=>{b.exports=a.x("node:path",()=>require("node:path"))},43515,a=>{"use strict";a.s(["formatUptime",0,function(a){let b=Math.floor(a/3600),c=Math.floor(a%3600/60);return b>0?`${b}h ${c}m`:`${c}m`},"timeAgo",0,function(a){if(!a)return"--";let b=Math.floor((Date.now()-new Date(a.replace(" ","T")+"Z").getTime())/1e3);return b<60?`${b}s ago`:b<3600?`${Math.floor(b/60)}m ago`:b<86400?`${Math.floor(b/3600)}h ago`:`${Math.floor(b/86400)}d ago`}])},53142,a=>{"use strict";var b=a.i(87924),c=a.i(38246);let d={nodes:{title:"No agents in this network",sub:"Agent sessions will appear here once they connect to the CommHub."},tasks:{title:"No tasks yet",sub:"Tasks will appear here when agents send them via CommHub."},messages:{title:"No messages",sub:"Messages between agents will appear here."},logs:{title:"No audit logs",sub:"Events will appear here when users register, login, or perform actions."},tokens:{title:"No API tokens",sub:"Create one to authenticate CLI tools and external integrations."},networks:{title:"No networks found",sub:"Create one or sign in with V3 auth to see your networks."},generic:{title:"Nothing here yet",sub:"Data will appear here once available."}};function e({variant:a,size:c}){let d={width:c,height:c},f={fill:"none",stroke:"currentColor",strokeWidth:1.25,strokeLinecap:"round",strokeLinejoin:"round"};switch(a){case"nodes":return(0,b.jsx)("svg",{viewBox:"0 0 64 64",...d,children:(0,b.jsxs)("g",{...f,children:[(0,b.jsx)("circle",{cx:"32",cy:"14",r:"4"}),(0,b.jsx)("circle",{cx:"14",cy:"44",r:"4"}),(0,b.jsx)("circle",{cx:"50",cy:"44",r:"4"}),(0,b.jsx)("line",{x1:"32",y1:"18",x2:"14",y2:"40",strokeDasharray:"3 3",opacity:"0.6"}),(0,b.jsx)("line",{x1:"32",y1:"18",x2:"50",y2:"40",strokeDasharray:"3 3",opacity:"0.6"}),(0,b.jsx)("line",{x1:"18",y1:"44",x2:"46",y2:"44",strokeDasharray:"3 3",opacity:"0.6"})]})});case"tasks":return(0,b.jsx)("svg",{viewBox:"0 0 64 64",...d,children:(0,b.jsxs)("g",{...f,children:[(0,b.jsx)("rect",{x:"14",y:"14",width:"36",height:"6",rx:"1.5"}),(0,b.jsx)("rect",{x:"14",y:"26",width:"36",height:"6",rx:"1.5",opacity:"0.6"}),(0,b.jsx)("rect",{x:"14",y:"38",width:"36",height:"6",rx:"1.5",opacity:"0.35"})]})});case"messages":return(0,b.jsx)("svg",{viewBox:"0 0 64 64",...d,children:(0,b.jsxs)("g",{...f,children:[(0,b.jsx)("path",{d:"M14 18 h36 a3 3 0 0 1 3 3 v18 a3 3 0 0 1 -3 3 h-18 l-8 6 v-6 h-10 a3 3 0 0 1 -3 -3 v-18 a3 3 0 0 1 3 -3 z"}),(0,b.jsx)("line",{x1:"22",y1:"28",x2:"42",y2:"28",opacity:"0.5"}),(0,b.jsx)("line",{x1:"22",y1:"34",x2:"36",y2:"34",opacity:"0.5"})]})});case"logs":return(0,b.jsx)("svg",{viewBox:"0 0 64 64",...d,children:(0,b.jsxs)("g",{...f,children:[(0,b.jsx)("path",{d:"M18 12 h22 l8 8 v32 a2 2 0 0 1 -2 2 h-28 a2 2 0 0 1 -2 -2 v-38 a2 2 0 0 1 2 -2 z"}),(0,b.jsx)("path",{d:"M40 12 v8 h8",opacity:"0.6"}),(0,b.jsx)("line",{x1:"24",y1:"32",x2:"40",y2:"32",opacity:"0.55"}),(0,b.jsx)("line",{x1:"24",y1:"38",x2:"40",y2:"38",opacity:"0.4"}),(0,b.jsx)("line",{x1:"24",y1:"44",x2:"34",y2:"44",opacity:"0.25"})]})});case"tokens":return(0,b.jsx)("svg",{viewBox:"0 0 64 64",...d,children:(0,b.jsxs)("g",{...f,children:[(0,b.jsx)("circle",{cx:"22",cy:"32",r:"8"}),(0,b.jsx)("line",{x1:"30",y1:"32",x2:"54",y2:"32"}),(0,b.jsx)("line",{x1:"44",y1:"32",x2:"44",y2:"38"}),(0,b.jsx)("line",{x1:"50",y1:"32",x2:"50",y2:"40"})]})});case"networks":return(0,b.jsx)("svg",{viewBox:"0 0 64 64",...d,children:(0,b.jsxs)("g",{...f,children:[(0,b.jsx)("circle",{cx:"32",cy:"32",r:"18"}),(0,b.jsx)("ellipse",{cx:"32",cy:"32",rx:"18",ry:"9",opacity:"0.55"}),(0,b.jsx)("line",{x1:"14",y1:"32",x2:"50",y2:"32",opacity:"0.55"}),(0,b.jsx)("line",{x1:"32",y1:"14",x2:"32",y2:"50",opacity:"0.55"})]})});default:return(0,b.jsx)("svg",{viewBox:"0 0 64 64",...d,children:(0,b.jsxs)("g",{...f,children:[(0,b.jsx)("circle",{cx:"32",cy:"32",r:"14",strokeDasharray:"3 3",opacity:"0.6"}),(0,b.jsx)("circle",{cx:"32",cy:"32",r:"3"})]})})}}function f({variant:a="generic",title:g,sub:h,cta:i,compact:j=!1}){let k=d[a],l=g??k.title,m=h??k.sub;return(0,b.jsxs)("div",{className:`text-center ${j?"py-8":"py-16"} px-4`,role:"status",children:[(0,b.jsx)("div",{className:"anet-empty-glyph inline-flex items-center justify-center mb-4 text-gray-500","aria-hidden":!0,children:(0,b.jsx)(e,{variant:a,size:j?40:56})}),(0,b.jsx)("h3",{className:`font-medium text-gray-300 ${j?"text-sm":"text-base"}`,children:l}),m&&(0,b.jsx)("p",{className:`text-gray-500 ${j?"text-xs mt-1.5":"text-sm mt-2"} max-w-md mx-auto leading-relaxed`,children:m}),i&&(0,b.jsx)("div",{className:"mt-4",children:i.href?(0,b.jsxs)(c.default,{href:i.href,className:"inline-flex items-center gap-1.5 text-sm font-medium text-cyan-400 hover:text-cyan-300",children:[i.label,(0,b.jsx)("span",{"aria-hidden":!0,children:"→"})]}):(0,b.jsxs)("button",{onClick:i.onClick,className:"inline-flex items-center gap-1.5 text-sm font-medium text-cyan-400 hover:text-cyan-300",children:[i.label,(0,b.jsx)("span",{"aria-hidden":!0,children:"→"})]})})]})}a.s(["EmptyState",0,f,"NodesEmptyState",0,function({hint:a}){return a?.global_count?(0,b.jsx)(f,{variant:"nodes",title:"No agents in this network",sub:`Server has ${a.global_count} nodes globally, but none are registered to the current network. Switch network or contact admin.`}):(0,b.jsx)(f,{variant:"nodes",cta:{label:"Run anet quickstart",href:"https://anet.sh"}})}])},44332,a=>{"use strict";var b=a.i(72596),c=a.i(22397);let d=async a=>{let b=await fetch(a);if(401===b.status)throw window.location.assign("/login"),Error("unauthorized");return b.json()},e={refreshInterval:5e3,dedupingInterval:3e3};function f(a,b){if(!b)return a;let c=a.includes("?")?"&":"?";return`${a}${c}network_id=${encodeURIComponent(b)}`}a.s(["useAnetConfig",0,function(){let{data:a}=(0,b.default)("/api/anet/config",d,{refreshInterval:3e4});return{config:a||null}},"useHealth",0,function(){let{data:a,error:c}=(0,b.default)("/api/hub/health",d,e);return{health:a||null,error:c}},"useLicense",0,function(){let{data:a,error:c}=(0,b.default)("/api/hub/license",d,{refreshInterval:6e4});return{license:a?.ok?a:null,error:c}},"useMessages",0,function(a=100){let{networkId:g}=(0,c.useNetworkId)(),{data:h,error:i,isLoading:j}=(0,b.default)(f(`/api/hub/messages?limit=${a}`,g),d,e);return{messages:h?.messages||[],error:i,isLoading:j}},"useSessions",0,function(){let{networkId:a}=(0,c.useNetworkId)(),{data:g,error:h,isLoading:i}=(0,b.default)(f("/api/hub/status",a),d,e);return{sessions:g?.sessions||[],hint:g?._hint,error:h,isLoading:i}},"useStats",0,function(){let{networkId:a}=(0,c.useNetworkId)(),{data:g,error:h}=(0,b.default)(f("/api/hub/stats",a),d,e);return{stats:g?.ok?g:null,error:h}},"useTasks",0,function(a){let{networkId:g}=(0,c.useNetworkId)(),h=new URLSearchParams({limit:"100",...a}).toString(),{data:i,error:j,isLoading:k}=(0,b.default)(f(`/api/hub/tasks?${h}`,g),d,e);return{tasks:i?.tasks||[],count:i?.count??0,source:i?.source,error:j,isLoading:k}}])},54826,(a,b,c)=>{},15299,(a,b,c)=>{a.r(54826);var d=a.r(72131),e=d&&"object"==typeof d&&"default"in d?d:{default:d},f="u">typeof process&&process.env&&!0,g=function(a){return"[object String]"===Object.prototype.toString.call(a)},h=function(){function a(a){var b=void 0===a?{}:a,c=b.name,d=void 0===c?"stylesheet":c,e=b.optimizeForSpeed,h=void 0===e?f:e;i(g(d),"`name` must be a string"),this._name=d,this._deletedRulePlaceholder="#"+d+"-deleted-rule____{}",i("boolean"==typeof h,"`optimizeForSpeed` must be a boolean"),this._optimizeForSpeed=h,this._serverSheet=void 0,this._tags=[],this._injected=!1,this._rulesCount=0,this._nonce=null}var b,c=a.prototype;return c.setOptimizeForSpeed=function(a){i("boolean"==typeof a,"`setOptimizeForSpeed` accepts a boolean"),i(0===this._rulesCount,"optimizeForSpeed cannot be when rules have already been inserted"),this.flush(),this._optimizeForSpeed=a,this.inject()},c.isOptimizeForSpeed=function(){return this._optimizeForSpeed},c.inject=function(){var a=this;i(!this._injected,"sheet already injected"),this._injected=!0,this._serverSheet={cssRules:[],insertRule:function(b,c){return"number"==typeof c?a._serverSheet.cssRules[c]={cssText:b}:a._serverSheet.cssRules.push({cssText:b}),c},deleteRule:function(b){a._serverSheet.cssRules[b]=null}}},c.getSheetForTag=function(a){if(a.sheet)return a.sheet;for(var b=0;b<document.styleSheets.length;b++)if(document.styleSheets[b].ownerNode===a)return document.styleSheets[b]},c.getSheet=function(){return this.getSheetForTag(this._tags[this._tags.length-1])},c.insertRule=function(a,b){return i(g(a),"`insertRule` accepts only strings"),"number"!=typeof b&&(b=this._serverSheet.cssRules.length),this._serverSheet.insertRule(a,b),this._rulesCount++},c.replaceRule=function(a,b){this._optimizeForSpeed;var c=this._serverSheet;if(b.trim()||(b=this._deletedRulePlaceholder),!c.cssRules[a])return a;c.deleteRule(a);try{c.insertRule(b,a)}catch(d){f||console.warn("StyleSheet: illegal rule: \n\n"+b+"\n\nSee https://stackoverflow.com/q/20007992 for more info"),c.insertRule(this._deletedRulePlaceholder,a)}return a},c.deleteRule=function(a){this._serverSheet.deleteRule(a)},c.flush=function(){this._injected=!1,this._rulesCount=0,this._serverSheet.cssRules=[]},c.cssRules=function(){return this._serverSheet.cssRules},c.makeStyleTag=function(a,b,c){b&&i(g(b),"makeStyleTag accepts only strings as second parameter");var d=document.createElement("style");this._nonce&&d.setAttribute("nonce",this._nonce),d.type="text/css",d.setAttribute("data-"+a,""),b&&d.appendChild(document.createTextNode(b));var e=document.head||document.getElementsByTagName("head")[0];return c?e.insertBefore(d,c):e.appendChild(d),d},b=[{key:"length",get:function(){return this._rulesCount}}],function(a,b){for(var c=0;c<b.length;c++){var d=b[c];d.enumerable=d.enumerable||!1,d.configurable=!0,"value"in d&&(d.writable=!0),Object.defineProperty(a,d.key,d)}}(a.prototype,b),a}();function i(a,b){if(!a)throw Error("StyleSheet: "+b+".")}var j=function(a){for(var b=5381,c=a.length;c;)b=33*b^a.charCodeAt(--c);return b>>>0},k={};function l(a,b){if(!b)return"jsx-"+a;var c=String(b),d=a+c;return k[d]||(k[d]="jsx-"+j(a+"-"+c)),k[d]}function m(a,b){var c=a+(b=b.replace(/\/style/gi,"\\/style"));return k[c]||(k[c]=b.replace(/__jsx-style-dynamic-selector/g,a)),k[c]}var n=function(){function a(a){var b=void 0===a?{}:a,c=b.styleSheet,d=void 0===c?null:c,e=b.optimizeForSpeed,f=void 0!==e&&e;this._sheet=d||new h({name:"styled-jsx",optimizeForSpeed:f}),this._sheet.inject(),d&&"boolean"==typeof f&&(this._sheet.setOptimizeForSpeed(f),this._optimizeForSpeed=this._sheet.isOptimizeForSpeed()),this._fromServer=void 0,this._indices={},this._instancesCounts={}}var b=a.prototype;return b.add=function(a){var b=this;void 0===this._optimizeForSpeed&&(this._optimizeForSpeed=Array.isArray(a.children),this._sheet.setOptimizeForSpeed(this._optimizeForSpeed),this._optimizeForSpeed=this._sheet.isOptimizeForSpeed());var c=this.getIdAndRules(a),d=c.styleId,e=c.rules;if(d in this._instancesCounts){this._instancesCounts[d]+=1;return}var f=e.map(function(a){return b._sheet.insertRule(a)}).filter(function(a){return -1!==a});this._indices[d]=f,this._instancesCounts[d]=1},b.remove=function(a){var b=this,c=this.getIdAndRules(a).styleId;if(function(a,b){if(!a)throw Error("StyleSheetRegistry: "+b+".")}(c in this._instancesCounts,"styleId: `"+c+"` not found"),this._instancesCounts[c]-=1,this._instancesCounts[c]<1){var d=this._fromServer&&this._fromServer[c];d?(d.parentNode.removeChild(d),delete this._fromServer[c]):(this._indices[c].forEach(function(a){return b._sheet.deleteRule(a)}),delete this._indices[c]),delete this._instancesCounts[c]}},b.update=function(a,b){this.add(b),this.remove(a)},b.flush=function(){this._sheet.flush(),this._sheet.inject(),this._fromServer=void 0,this._indices={},this._instancesCounts={}},b.cssRules=function(){var a=this,b=this._fromServer?Object.keys(this._fromServer).map(function(b){return[b,a._fromServer[b]]}):[],c=this._sheet.cssRules();return b.concat(Object.keys(this._indices).map(function(b){return[b,a._indices[b].map(function(a){return c[a].cssText}).join(a._optimizeForSpeed?"":"\n")]}).filter(function(a){return!!a[1]}))},b.styles=function(a){var b,c;return b=this.cssRules(),void 0===(c=a)&&(c={}),b.map(function(a){var b=a[0],d=a[1];return e.default.createElement("style",{id:"__"+b,key:"__"+b,nonce:c.nonce?c.nonce:void 0,dangerouslySetInnerHTML:{__html:d}})})},b.getIdAndRules=function(a){var b=a.children,c=a.dynamic,d=a.id;if(c){var e=l(d,c);return{styleId:e,rules:Array.isArray(b)?b.map(function(a){return m(e,a)}):[m(e,b)]}}return{styleId:l(d),rules:Array.isArray(b)?b:[b]}},b.selectFromServer=function(){return Array.prototype.slice.call(document.querySelectorAll('[id^="__jsx-"]')).reduce(function(a,b){return a[b.id.slice(2)]=b,a},{})},a}(),o=d.createContext(null);function p(){return new n}function q(){return d.useContext(o)}function r(a){var b=q();return b&&b.add(a),null}o.displayName="StyleSheetContext",e.default.useInsertionEffect||e.default.useLayoutEffect,r.dynamic=function(a){return a.map(function(a){return l(a[0],a[1])}).join(" ")},c.StyleRegistry=function(a){var b=a.registry,c=a.children,f=d.useContext(o),g=d.useState(function(){return f||b||p()})[0];return e.default.createElement(o.Provider,{value:g},c)},c.createStyleRegistry=p,c.style=r,c.useStyleRegistry=q},31626,(a,b,c)=>{b.exports=a.r(15299).style}];
2
-
3
- //# sourceMappingURL=%5Broot-of-the-server%5D__0_jnflc._.js.map