@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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +50 -50
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-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_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +4 -4
- 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.js.nft.json +1 -1
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +4 -4
- 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 +4 -4
- 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.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.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +4 -4
- 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.js.nft.json +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +4 -4
- 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.js.nft.json +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +4 -4
- 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.js.nft.json +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +4 -4
- 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.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +4 -4
- 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.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 +4 -4
- 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.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +4 -4
- 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 +4 -4
- 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_client-reference-manifest.js +1 -1
- 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 +4 -4
- 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]__02ee4km._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__02ee4km._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0n4l2is._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0n4l2is._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0o8yj3c._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0o8yj3c._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js.map +1 -1
- package/.next/server/chunks/ssr/_0nne7mz._.js +3 -0
- package/.next/server/chunks/ssr/_0nne7mz._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0.zbtn5._.js +9 -0
- package/.next/server/chunks/ssr/app_0.zbtn5._.js.map +1 -0
- package/.next/server/chunks/ssr/app_04n78pr._.js +3 -0
- package/.next/server/chunks/ssr/app_04n78pr._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0_agk0r._.js +3 -0
- package/.next/server/chunks/ssr/app_0_agk0r._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0aum_g5._.js +3 -0
- package/.next/server/chunks/ssr/app_0aum_g5._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0esuljp._.js +3 -0
- package/.next/server/chunks/ssr/app_0esuljp._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0izi28j._.js +1 -1
- package/.next/server/chunks/ssr/app_0izi28j._.js.map +1 -1
- package/.next/server/chunks/ssr/app_0py1sns._.js +3 -0
- package/.next/server/chunks/ssr/app_0py1sns._.js.map +1 -0
- package/.next/server/chunks/ssr/app_0tncr1_._.js +3 -0
- package/.next/server/chunks/ssr/app_0tncr1_._.js.map +1 -0
- package/.next/server/chunks/ssr/app_components_08ys5y3._.js +5 -5
- package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +1 -1
- package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js +1 -1
- package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.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/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +4 -4
- 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/000qcyr-yo73c.css +2 -0
- package/.next/static/chunks/02oje7pf-g1v5.js +1 -0
- package/.next/static/chunks/06x7w8nwggx~~.js +1 -0
- package/.next/static/chunks/{07okhs5561y5y.js → 0c1bp~g7.8ru..js} +1 -1
- package/.next/static/chunks/0edt_z0rlxgfk.js +1 -0
- package/.next/static/chunks/0irkayokwc1fm.js +1 -0
- package/.next/static/chunks/0j8ee64jv6zb7.js +7 -0
- package/.next/static/chunks/0l2rkodu2.v~r.js +1 -0
- package/.next/static/chunks/0mdu-2u9-dx.4.js +1 -0
- package/.next/static/chunks/0uar..avu998d.js +1 -0
- package/.next/static/chunks/0vqk_f6kf2hnl.js +1 -0
- package/.next/static/chunks/0xu37uygp5fs-.js +1 -0
- package/.next/static/chunks/0~n.cadw9_e8s.js +7 -0
- package/.next/static/chunks/12hyhrnzrx~ys.js +1 -0
- package/.next/static/chunks/1528e.3d.l1ve.js +1 -0
- package/.next/static/chunks/15c9pv6gxir.5.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/admin/page.tsx +77 -25
- package/app/components/AgentCard.tsx +19 -5
- package/app/components/AliasAvatar.tsx +51 -0
- package/app/components/BroadcastBar.tsx +22 -4
- package/app/components/DispatchPanel.tsx +9 -5
- package/app/components/EmptyState.tsx +77 -4
- package/app/components/HealthBanner.tsx +1 -1
- package/app/components/HelpOverlay.tsx +50 -10
- package/app/components/InboxPanel.tsx +6 -3
- package/app/components/Sidebar.tsx +49 -41
- package/app/components/TaskChatPanel.tsx +8 -10
- package/app/components/TaskDrawer.tsx +72 -28
- package/app/components/TopoGraph.tsx +118 -24
- package/app/globals.css +90 -0
- package/app/lib/status.ts +63 -0
- package/app/lib/version.ts +7 -0
- package/app/login/page.tsx +40 -7
- package/app/logs/page.tsx +72 -21
- package/app/messages/page.tsx +8 -33
- package/app/node/page.tsx +48 -25
- package/app/nodes/page.tsx +21 -23
- package/app/page.tsx +77 -17
- package/app/server-logs/page.tsx +70 -12
- package/app/settings/networks/page.tsx +37 -13
- package/app/settings/page.tsx +38 -10
- package/app/tasks/page.tsx +90 -93
- package/package.json +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0_jnflc._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__12mpoig._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__12mpoig._.js.map +0 -1
- package/.next/server/chunks/ssr/_0d-fuox._.js +0 -3
- package/.next/server/chunks/ssr/_0d-fuox._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0.0wpa~._.js +0 -3
- package/.next/server/chunks/ssr/app_0.0wpa~._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0ap_oiq._.js +0 -3
- package/.next/server/chunks/ssr/app_0ap_oiq._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0dk5wao._.js +0 -3
- package/.next/server/chunks/ssr/app_0dk5wao._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0et5yua._.js +0 -3
- package/.next/server/chunks/ssr/app_0et5yua._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0ff~kjj._.js +0 -9
- package/.next/server/chunks/ssr/app_0ff~kjj._.js.map +0 -1
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +0 -3
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +0 -1
- package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js +0 -3
- package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js.map +0 -1
- package/.next/static/chunks/02flrewxlf_m_.js +0 -1
- package/.next/static/chunks/051gslfjka1kj.js +0 -1
- package/.next/static/chunks/06t_vzei8s9_h.js +0 -1
- package/.next/static/chunks/07oobuohrpigy.js +0 -1
- package/.next/static/chunks/0e7c40p~wq665.js +0 -1
- package/.next/static/chunks/0g7m7zk.v7g6h.js +0 -1
- package/.next/static/chunks/0khpyxpdrp~ty.js +0 -1
- package/.next/static/chunks/0m328uz90rp81.js +0 -1
- package/.next/static/chunks/0ogeals5ufhho.js +0 -1
- package/.next/static/chunks/0svy-tmgb8ajq.js +0 -7
- package/.next/static/chunks/1031v6nw33ud3.js +0 -1
- package/.next/static/chunks/10ir.g.n~hre0.js +0 -7
- package/.next/static/chunks/10njj7kn9ylkf.js +0 -1
- package/.next/static/chunks/13.dst7liziwo.js +0 -1
- package/.next/static/chunks/168ijr_r0ku-q.css +0 -2
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/public/window.svg +0 -1
- /package/.next/static/{HWCPpUEba8WqttkPXD5x8 → AQXM9jmgIyScbdEjjvrFL}/_buildManifest.js +0 -0
- /package/.next/static/{HWCPpUEba8WqttkPXD5x8 → AQXM9jmgIyScbdEjjvrFL}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{HWCPpUEba8WqttkPXD5x8 → AQXM9jmgIyScbdEjjvrFL}/_ssgManifest.js +0 -0
package/app/logs/page.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
|
|
5
5
|
import { timeAgo } from '../components/utils';
|
|
6
6
|
import { useNetworkId } from '../lib/network-context';
|
|
7
7
|
import { EmptyState } from '../components/EmptyState';
|
|
8
|
+
import { AliasAvatar } from '../components/AliasAvatar';
|
|
8
9
|
|
|
9
10
|
interface AuditLog {
|
|
10
11
|
id: string;
|
|
@@ -26,6 +27,18 @@ const ACTION_COLORS: Record<string, string> = {
|
|
|
26
27
|
send_task: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20',
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
/** 2px left-rail stripe color per audit action — reuses the r32 server-logs
|
|
31
|
+
* pattern so failed logins / token rotations spike out of a wall of
|
|
32
|
+
* registers and logins without users reading every chip. Inline hex
|
|
33
|
+
* prevents Tailwind v4 from purging dynamic class names. */
|
|
34
|
+
const ACTION_STRIPE: Record<string, string> = {
|
|
35
|
+
register: '#4ade80',
|
|
36
|
+
login: '#60a5fa',
|
|
37
|
+
login_failed: '#ef4444',
|
|
38
|
+
create_network: '#a78bfa',
|
|
39
|
+
send_task: '#22d3ee',
|
|
40
|
+
};
|
|
41
|
+
|
|
29
42
|
export default function LogsPage() {
|
|
30
43
|
const { networkId } = useNetworkId();
|
|
31
44
|
const [logs, setLogs] = useState<AuditLog[]>([]);
|
|
@@ -64,25 +77,52 @@ export default function LogsPage() {
|
|
|
64
77
|
return (
|
|
65
78
|
<div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
|
|
66
79
|
<div className="flex items-center gap-4 mb-6">
|
|
67
|
-
<h1 className="text-2xl font-bold text-white lg:ml-0 ml-10">
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
<h1 className="text-2xl font-bold text-white lg:ml-0 ml-10">Audit Log</h1>
|
|
81
|
+
{logs.length > 0 && (
|
|
82
|
+
<span className="text-xs bg-blue-900/30 text-blue-400 px-2 py-0.5 rounded-full border border-blue-800/30">
|
|
83
|
+
{logs.length}
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
71
86
|
</div>
|
|
72
87
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
{/* Action filter chips (round 43) — same chip pattern as Tasks
|
|
89
|
+
status tabs / Overview agent filter. Each chip carries its
|
|
90
|
+
ACTION_STRIPE dot so the visual key matches the per-row left
|
|
91
|
+
rail. Counts come from the currently loaded log set. */}
|
|
92
|
+
<div className="mb-6 flex flex-wrap items-center gap-1 anet-tabstrip-wrap overflow-x-auto sm:overflow-x-visible">
|
|
93
|
+
{(() => {
|
|
94
|
+
const counts: Record<string, number> = {};
|
|
95
|
+
logs.forEach(l => { counts[l.action] = (counts[l.action] || 0) + 1; });
|
|
96
|
+
const chips: { key: string; label: string; dot?: string }[] = [
|
|
97
|
+
{ key: '', label: 'All' },
|
|
98
|
+
{ key: 'login', label: 'login', dot: ACTION_STRIPE.login },
|
|
99
|
+
{ key: 'login_failed', label: 'login_failed', dot: ACTION_STRIPE.login_failed },
|
|
100
|
+
{ key: 'register', label: 'register', dot: ACTION_STRIPE.register },
|
|
101
|
+
{ key: 'create_network', label: 'create_network', dot: ACTION_STRIPE.create_network },
|
|
102
|
+
{ key: 'send_task', label: 'send_task', dot: ACTION_STRIPE.send_task },
|
|
103
|
+
];
|
|
104
|
+
return chips.map(c => {
|
|
105
|
+
const count = c.key === '' ? logs.length : counts[c.key] || 0;
|
|
106
|
+
const isActive = filterAction === c.key;
|
|
107
|
+
return (
|
|
108
|
+
<button
|
|
109
|
+
key={c.key || 'all'}
|
|
110
|
+
type="button"
|
|
111
|
+
onClick={() => setFilterAction(c.key)}
|
|
112
|
+
disabled={count === 0 && c.key !== ''}
|
|
113
|
+
className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors disabled:opacity-30 disabled:cursor-not-allowed shrink-0 whitespace-nowrap ${
|
|
114
|
+
isActive
|
|
115
|
+
? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/30'
|
|
116
|
+
: 'text-gray-500 hover:text-gray-200 hover:bg-[#1a1a2a] border border-transparent'
|
|
117
|
+
}`}
|
|
118
|
+
>
|
|
119
|
+
{c.dot && <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full" style={{ backgroundColor: c.dot }} />}
|
|
120
|
+
<span>{c.label}</span>
|
|
121
|
+
<span className={`text-[10px] tabular-nums ${isActive ? 'text-cyan-400' : 'text-gray-600'}`}>{count}</span>
|
|
122
|
+
</button>
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
})()}
|
|
86
126
|
</div>
|
|
87
127
|
|
|
88
128
|
{error && (() => {
|
|
@@ -125,13 +165,23 @@ export default function LogsPage() {
|
|
|
125
165
|
<EmptyState variant="logs" />
|
|
126
166
|
) : (
|
|
127
167
|
<div className="space-y-2">
|
|
128
|
-
{logs.map(log =>
|
|
129
|
-
|
|
168
|
+
{logs.map(log => {
|
|
169
|
+
const userName = log.username || log.user_id;
|
|
170
|
+
return (
|
|
171
|
+
<div key={log.id} className="relative bg-[#111128] border border-[#2a2a4a] rounded-lg pl-4 pr-4 py-3 hover:border-[#3a3a5a] transition-colors overflow-hidden">
|
|
172
|
+
{/* 2px left rail per action (round 33) — failed logins, token
|
|
173
|
+
rotations spike out of a wall of register/login rows. */}
|
|
174
|
+
<span
|
|
175
|
+
aria-hidden
|
|
176
|
+
className="absolute left-0 top-0 bottom-0 w-0.5"
|
|
177
|
+
style={{ backgroundColor: ACTION_STRIPE[log.action] || 'transparent' }}
|
|
178
|
+
/>
|
|
130
179
|
<div className="flex flex-wrap items-center gap-2 mb-1">
|
|
131
180
|
<span className={`text-xs px-2 py-0.5 rounded-md border ${ACTION_COLORS[log.action] || 'bg-gray-500/10 text-gray-400 border-gray-500/20'}`}>
|
|
132
181
|
{log.action}
|
|
133
182
|
</span>
|
|
134
|
-
<
|
|
183
|
+
{userName && <AliasAvatar alias={userName} size={16} />}
|
|
184
|
+
<span className="text-sm text-gray-200 font-medium">{userName}</span>
|
|
135
185
|
{log.target_type && (
|
|
136
186
|
<>
|
|
137
187
|
<span className="text-xs text-gray-600">→</span>
|
|
@@ -143,7 +193,8 @@ export default function LogsPage() {
|
|
|
143
193
|
{log.detail && <div className="text-xs text-gray-500">{log.detail}</div>}
|
|
144
194
|
{log.ip && log.ip !== 'unknown' && <div className="text-[10px] text-gray-700">IP: {log.ip}</div>}
|
|
145
195
|
</div>
|
|
146
|
-
|
|
196
|
+
);
|
|
197
|
+
})}
|
|
147
198
|
</div>
|
|
148
199
|
)}
|
|
149
200
|
</div>
|
package/app/messages/page.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
|
|
|
4
4
|
import { useMessages } from '../lib/hooks';
|
|
5
5
|
import { timeAgo } from '../components/utils';
|
|
6
6
|
import { EmptyState } from '../components/EmptyState';
|
|
7
|
+
import { AliasAvatar } from '../components/AliasAvatar';
|
|
7
8
|
|
|
8
9
|
interface MessageItem {
|
|
9
10
|
id: string;
|
|
@@ -40,26 +41,6 @@ function bubbleVariant(message: MessageItem) {
|
|
|
40
41
|
return 'incoming';
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
/** Stable color hash for sender avatars — same alias always gets the same
|
|
44
|
-
* ring, so users learn the colors over time. 8 hues, evenly spread. */
|
|
45
|
-
const AVATAR_HUES = [180, 200, 220, 270, 300, 330, 30, 90];
|
|
46
|
-
function avatarColors(alias: string): { bg: string; ring: string; text: string } {
|
|
47
|
-
let h = 0;
|
|
48
|
-
for (let i = 0; i < alias.length; i++) h = (h * 31 + alias.charCodeAt(i)) >>> 0;
|
|
49
|
-
const hue = AVATAR_HUES[h % AVATAR_HUES.length];
|
|
50
|
-
return {
|
|
51
|
-
bg: `hsl(${hue} 55% 22%)`,
|
|
52
|
-
ring: `hsl(${hue} 60% 45%)`,
|
|
53
|
-
text: `hsl(${hue} 80% 78%)`,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function avatarInitial(alias?: string): string {
|
|
57
|
-
if (!alias) return '·';
|
|
58
|
-
// Strip non-letter prefix, take first non-ws char; handle CJK directly.
|
|
59
|
-
const ch = alias.trim().match(/[\p{L}\p{N}]/u)?.[0] || alias.trim()[0] || '·';
|
|
60
|
-
return ch.toUpperCase();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
44
|
/** Render message content, highlighting the search match inline (skips the
|
|
64
45
|
* `from:` filter prefix since it filters by sender, not by content). */
|
|
65
46
|
function renderHighlighted(content: string | undefined, search: string) {
|
|
@@ -225,16 +206,19 @@ export default function MessagesPage() {
|
|
|
225
206
|
<div key={ci} className="bg-[#111128] border border-[#2a2a4a] rounded-xl overflow-hidden">
|
|
226
207
|
<div className="px-4 py-2.5 border-b border-[#2a2a4a] flex items-center justify-between">
|
|
227
208
|
<div className="flex items-center gap-2">
|
|
228
|
-
<
|
|
209
|
+
<AliasAvatar alias={conv.participants[0]} size={16} />
|
|
210
|
+
<span className="text-sm text-gray-200 font-medium">{conv.participants[0]}</span>
|
|
229
211
|
<span className="text-gray-600 text-xs">↔</span>
|
|
230
|
-
<
|
|
212
|
+
<AliasAvatar alias={conv.participants[1]} size={16} />
|
|
213
|
+
<span className="text-sm text-gray-200 font-medium">{conv.participants[1]}</span>
|
|
231
214
|
</div>
|
|
232
215
|
<span className="text-[10px] text-gray-600">{conv.messages.length} messages</span>
|
|
233
216
|
</div>
|
|
234
217
|
<div className="px-4 py-2 space-y-2 max-h-64 overflow-y-auto">
|
|
235
218
|
{conv.messages.map(m => (
|
|
236
219
|
<div key={m.id} className="flex items-start gap-2 text-xs py-1">
|
|
237
|
-
|
|
220
|
+
{m.from_alias && <AliasAvatar alias={m.from_alias} size={14} />}
|
|
221
|
+
<span className="shrink-0 font-medium text-gray-200">{m.from_alias}</span>
|
|
238
222
|
<span className="text-gray-400 flex-1">{m.content?.slice(0, 120) || '--'}</span>
|
|
239
223
|
<span className="text-[9px] text-gray-600 shrink-0">{timeAgo(m.created_at || '')}</span>
|
|
240
224
|
</div>
|
|
@@ -259,7 +243,6 @@ export default function MessagesPage() {
|
|
|
259
243
|
&& (previous.from_alias || '') === (message.from_alias || '')
|
|
260
244
|
&& (previous.to_alias || '') === (message.to_alias || '');
|
|
261
245
|
const fromAlias = message.from_alias || '?';
|
|
262
|
-
const avColors = avatarColors(fromAlias);
|
|
263
246
|
|
|
264
247
|
return (
|
|
265
248
|
<div key={message.id}>
|
|
@@ -293,15 +276,7 @@ export default function MessagesPage() {
|
|
|
293
276
|
<div className={`${samePrev ? 'mt-1' : 'mt-3'} flex gap-2 ${variant === 'outgoing' ? 'flex-row-reverse' : 'flex-row'}`}>
|
|
294
277
|
{/* Avatar gutter — fixed width keeps bubble columns aligned even on streaks */}
|
|
295
278
|
<div className="w-8 shrink-0 pt-1">
|
|
296
|
-
{!samePrev &&
|
|
297
|
-
<div
|
|
298
|
-
className="flex h-8 w-8 items-center justify-center rounded-full border text-[11px] font-semibold"
|
|
299
|
-
style={{ backgroundColor: avColors.bg, borderColor: avColors.ring, color: avColors.text }}
|
|
300
|
-
title={fromAlias}
|
|
301
|
-
>
|
|
302
|
-
{avatarInitial(fromAlias)}
|
|
303
|
-
</div>
|
|
304
|
-
)}
|
|
279
|
+
{!samePrev && <AliasAvatar alias={fromAlias} size={32} />}
|
|
305
280
|
</div>
|
|
306
281
|
<div className={`min-w-0 max-w-3xl rounded-2xl border px-4 py-3 shadow-sm ${
|
|
307
282
|
variant === 'outgoing'
|
package/app/node/page.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import Link from 'next/link';
|
|
|
6
6
|
import { Suspense } from 'react';
|
|
7
7
|
import { TaskChatPanel } from '../components/TaskChatPanel';
|
|
8
8
|
import { timeAgo } from '../components/utils';
|
|
9
|
+
import { AliasAvatar } from '../components/AliasAvatar';
|
|
9
10
|
|
|
10
11
|
interface SessionDetail {
|
|
11
12
|
resume_id: string;
|
|
@@ -150,15 +151,23 @@ function NodeDetailContent() {
|
|
|
150
151
|
|
|
151
152
|
return (
|
|
152
153
|
<div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
|
|
153
|
-
{/* Header
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
<
|
|
161
|
-
|
|
154
|
+
{/* Header — round 40: avatar joins the alias for cross-page hue
|
|
155
|
+
consistency; agent type lives in a subtitle so users know what
|
|
156
|
+
kind of node they're looking at. */}
|
|
157
|
+
<div className="flex items-center gap-3 mb-6 lg:ml-0 ml-10">
|
|
158
|
+
<Link href="/nodes" className="text-gray-500 hover:text-gray-300 text-sm shrink-0">← Nodes</Link>
|
|
159
|
+
<AliasAvatar alias={alias} size={32} />
|
|
160
|
+
<div className="min-w-0 flex-1">
|
|
161
|
+
<div className="flex items-center gap-2">
|
|
162
|
+
<h1 className="text-xl sm:text-2xl font-bold text-white truncate">{alias}</h1>
|
|
163
|
+
<span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${
|
|
164
|
+
sse > 0 ? (session?.status === 'working' ? 'bg-green-500 animate-pulse' : 'bg-emerald-400') : 'bg-gray-500'
|
|
165
|
+
}`} />
|
|
166
|
+
<span className={`text-[10px] uppercase tracking-wide font-semibold ${statusColor}`}>{session?.status || 'unknown'}</span>
|
|
167
|
+
</div>
|
|
168
|
+
{session?.agent && (
|
|
169
|
+
<div className="text-xs text-gray-500 truncate">{session.agent}{session.server ? <> <span className="text-gray-700 mx-1">·</span> {session.server}</> : null}</div>
|
|
170
|
+
)}
|
|
162
171
|
</div>
|
|
163
172
|
</div>
|
|
164
173
|
|
|
@@ -190,14 +199,22 @@ function NodeFullPanel({ alias, session, sse, sendMsg, setSendMsg, sending, send
|
|
|
190
199
|
|
|
191
200
|
return (
|
|
192
201
|
<div className="bg-[#0d0d1a] border border-[#2a2a4a] rounded-xl overflow-hidden h-[calc(100vh-140px)] flex flex-col">
|
|
193
|
-
{/* Tab bar
|
|
202
|
+
{/* Tab bar — round 40: emoji icons (💬 📋 📊) replaced with stroke
|
|
203
|
+
SVG to drop the "AI generated" tell. */}
|
|
194
204
|
<div className="flex border-b border-[#2a2a4a] bg-[#0a0a15] shrink-0">
|
|
195
|
-
{([
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
205
|
+
{([
|
|
206
|
+
{ id: 'chat', label: 'Chat', icon: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' },
|
|
207
|
+
{ id: 'events', label: 'Events', icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8' },
|
|
208
|
+
{ id: 'info', label: 'Info', icon: 'M12 21a9 9 0 100-18 9 9 0 000 18z M12 8h.01 M11 12h1v4h1' },
|
|
209
|
+
] as const).map(t => (
|
|
210
|
+
<button key={t.id} onClick={() => setTab(t.id)}
|
|
211
|
+
className={`flex items-center gap-2 px-5 py-3 text-sm font-medium border-b-2 transition-colors ${
|
|
212
|
+
tab === t.id ? 'border-cyan-400 text-cyan-300' : 'border-transparent text-gray-500 hover:text-gray-300'
|
|
199
213
|
}`}>
|
|
200
|
-
|
|
214
|
+
<svg className="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
215
|
+
<path d={t.icon} />
|
|
216
|
+
</svg>
|
|
217
|
+
<span>{t.label}</span>
|
|
201
218
|
</button>
|
|
202
219
|
))}
|
|
203
220
|
</div>
|
|
@@ -299,13 +316,16 @@ function NodeFullPanel({ alias, session, sse, sendMsg, setSendMsg, sending, send
|
|
|
299
316
|
);
|
|
300
317
|
}
|
|
301
318
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
319
|
+
/** Round 59: emoji icons replaced with stroke SVG paths to drop the
|
|
320
|
+
* "AI generated" tell. Each path is sized to fit inside the 18px
|
|
321
|
+
* colored event dot. */
|
|
322
|
+
const EVENT_COLORS: Record<string, { dot: string; line: string; path: string }> = {
|
|
323
|
+
delivered: { dot: 'bg-blue-400', line: 'bg-blue-500/30', path: 'M12 5v14 M5 12l7 7 7-7' }, // down arrow
|
|
324
|
+
acked: { dot: 'bg-yellow-400', line: 'bg-yellow-500/30', path: 'M20 6 9 17l-5-5' }, // check
|
|
325
|
+
running: { dot: 'bg-green-400', line: 'bg-green-500/30', path: 'M13 2L4 14h6l-1 8 10-12h-6l1-8z' }, // bolt
|
|
326
|
+
replied: { dot: 'bg-purple-400', line: 'bg-purple-500/30', path: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' }, // chat bubble
|
|
327
|
+
failed: { dot: 'bg-red-400', line: 'bg-red-500/30', path: 'M18 6 6 18 M6 6l12 12' }, // X
|
|
328
|
+
created: { dot: 'bg-gray-400', line: 'bg-gray-500/30', path: 'M12 5v14 M5 12h14' }, // plus
|
|
309
329
|
};
|
|
310
330
|
|
|
311
331
|
function EventsTimeline({ events, loading }: { events: Array<{ id: number; event_type: string; from_status: string; to_status: string; detail: string; created_at: string; task_id?: string }>; loading: boolean }) {
|
|
@@ -334,9 +354,12 @@ function EventsTimeline({ events, loading }: { events: Array<{ id: number; event
|
|
|
334
354
|
const cfg = EVENT_COLORS[e.to_status] || EVENT_COLORS.created;
|
|
335
355
|
return (
|
|
336
356
|
<div key={e.id} className="relative pb-4 last:pb-0">
|
|
337
|
-
{/* Dot on line
|
|
338
|
-
|
|
339
|
-
|
|
357
|
+
{/* Dot on line — round 59: emoji icons replaced with
|
|
358
|
+
a 10px stroke SVG path matching event type. */}
|
|
359
|
+
<div className={`absolute -left-6 top-0.5 w-[18px] h-[18px] rounded-full ${cfg.dot} flex items-center justify-center z-10 border-2 border-[#111128]`}>
|
|
360
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#0a0a15" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
|
|
361
|
+
<path d={cfg.path} />
|
|
362
|
+
</svg>
|
|
340
363
|
</div>
|
|
341
364
|
{/* Content */}
|
|
342
365
|
<div className="ml-2">
|
package/app/nodes/page.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { timeAgo } from '../components/utils';
|
|
|
6
6
|
import { useSessions, useHealth } from '../lib/hooks';
|
|
7
7
|
import { TaskChatPanel } from '../components/TaskChatPanel';
|
|
8
8
|
import { EmptyState } from '../components/EmptyState';
|
|
9
|
+
import { AliasAvatar } from '../components/AliasAvatar';
|
|
9
10
|
import type { Session } from '../components/types';
|
|
10
11
|
|
|
11
12
|
const STATUS_COLORS: Record<string, string> = {
|
|
@@ -188,14 +189,18 @@ export default function NodesPage() {
|
|
|
188
189
|
</span>
|
|
189
190
|
</div>
|
|
190
191
|
)}
|
|
191
|
-
<div className="flex items-start
|
|
192
|
+
<div className="flex items-start gap-3">
|
|
193
|
+
<AliasAvatar alias={s.alias} size={36} />
|
|
192
194
|
<Link href={`/node?alias=${encodeURIComponent(s.alias)}`} className="min-w-0 flex-1 hover:text-cyan-300">
|
|
193
195
|
<div className="flex items-center gap-2">
|
|
194
196
|
<span className="truncate text-base font-semibold text-white">{s.alias}</span>
|
|
195
|
-
<span className={`shrink-0 rounded-md border px-2 py-0.5 text-[
|
|
197
|
+
<span className={`shrink-0 rounded-md border px-2 py-0.5 text-[10px] uppercase tracking-wide ${STATUS_COLORS[statusKey] || STATUS_COLORS.offline}`}>
|
|
196
198
|
{statusKey}
|
|
197
199
|
</span>
|
|
198
200
|
</div>
|
|
201
|
+
<div className="mt-0.5 truncate text-xs text-gray-500">
|
|
202
|
+
{(s.agent || '—')}<span className="text-gray-700 mx-1.5">·</span>{(s.server || '—')}
|
|
203
|
+
</div>
|
|
199
204
|
</Link>
|
|
200
205
|
<div className="flex gap-1.5 shrink-0">
|
|
201
206
|
<button type="button" onClick={() => setChatAlias(s.alias)}
|
|
@@ -209,25 +214,16 @@ export default function NodesPage() {
|
|
|
209
214
|
</div>
|
|
210
215
|
</div>
|
|
211
216
|
|
|
212
|
-
<div className="mt-3
|
|
213
|
-
<div className="flex justify-between gap-3">
|
|
214
|
-
<span>
|
|
215
|
-
<span className="
|
|
216
|
-
</div>
|
|
217
|
-
<div className="flex justify-between gap-3">
|
|
218
|
-
<span>Server</span>
|
|
219
|
-
<span className="truncate text-gray-300">{s.server || '--'}</span>
|
|
217
|
+
<div className="mt-3 rounded-lg border border-[#1a1a2a] bg-[#0a0a15] px-3 py-2 text-xs">
|
|
218
|
+
<div className="flex items-center justify-between gap-3">
|
|
219
|
+
<span className="text-[10px] uppercase tracking-wide text-gray-600">Current task</span>
|
|
220
|
+
<span className="text-[10px] text-gray-600">{timeAgo(s.last_seen_at || s.updated_at)}</span>
|
|
220
221
|
</div>
|
|
221
|
-
<div className=
|
|
222
|
-
|
|
223
|
-
<span className="text-gray-300">{timeAgo(s.last_seen_at || s.updated_at)}</span>
|
|
222
|
+
<div className={`mt-1 line-clamp-2 ${s.task ? 'text-gray-300' : 'text-gray-600 italic'}`}>
|
|
223
|
+
{s.task || 'No current task'}
|
|
224
224
|
</div>
|
|
225
225
|
</div>
|
|
226
226
|
|
|
227
|
-
<div className="mt-3 rounded-lg border border-[#1a1a2a] bg-[#0a0a15] px-3 py-2 text-xs text-gray-400">
|
|
228
|
-
{s.task ? s.task : 'No current task'}
|
|
229
|
-
</div>
|
|
230
|
-
|
|
231
227
|
{progress > 0 && progress < 100 && (
|
|
232
228
|
<div className="mt-3 h-1.5 overflow-hidden rounded-full bg-gray-800">
|
|
233
229
|
<div className="h-1.5 rounded-full bg-cyan-400 transition-all" style={{ width: `${progress}%` }} />
|
|
@@ -297,8 +293,9 @@ export default function NodesPage() {
|
|
|
297
293
|
</span>
|
|
298
294
|
</div>
|
|
299
295
|
<div className="col-span-2 min-w-0">
|
|
300
|
-
<Link href={`/node?alias=${encodeURIComponent(s.alias)}`} className="
|
|
301
|
-
{s.alias}
|
|
296
|
+
<Link href={`/node?alias=${encodeURIComponent(s.alias)}`} className="flex items-center gap-2 min-w-0 hover:text-cyan-300">
|
|
297
|
+
<AliasAvatar alias={s.alias} size={20} />
|
|
298
|
+
<span className="truncate text-sm font-medium text-white">{s.alias}</span>
|
|
302
299
|
</Link>
|
|
303
300
|
</div>
|
|
304
301
|
<div className="col-span-2 truncate text-xs text-gray-400">{s.agent || '--'}</div>
|
|
@@ -317,15 +314,16 @@ export default function NodesPage() {
|
|
|
317
314
|
</div>
|
|
318
315
|
</div>
|
|
319
316
|
<div className="sm:hidden space-y-2">
|
|
320
|
-
<div className="flex items-center
|
|
321
|
-
<
|
|
322
|
-
|
|
317
|
+
<div className="flex items-center gap-2.5">
|
|
318
|
+
<AliasAvatar alias={s.alias} size={28} />
|
|
319
|
+
<Link href={`/node?alias=${encodeURIComponent(s.alias)}`} className="min-w-0 flex-1 hover:text-cyan-300">
|
|
320
|
+
<div className="truncate text-sm font-medium text-white">{s.alias}</div>
|
|
321
|
+
<div className="truncate text-[10px] text-gray-500">{s.agent || '—'} · {timeAgo(s.last_seen_at || s.updated_at)}</div>
|
|
323
322
|
</Link>
|
|
324
323
|
<span className={`shrink-0 text-xs px-2 py-0.5 rounded-md border ${STATUS_COLORS[statusKey] || STATUS_COLORS.offline}`}>
|
|
325
324
|
{statusKey}
|
|
326
325
|
</span>
|
|
327
326
|
</div>
|
|
328
|
-
<div className="text-xs text-gray-500">{s.agent || '--'} · {timeAgo(s.last_seen_at || s.updated_at)}</div>
|
|
329
327
|
{s.task && <div className="truncate text-xs text-gray-500">{s.task}</div>}
|
|
330
328
|
<div className="flex gap-2">
|
|
331
329
|
<button type="button" onClick={() => setChatAlias(s.alias)}
|
package/app/page.tsx
CHANGED
|
@@ -10,6 +10,8 @@ import { AgentCard } from './components/AgentCard';
|
|
|
10
10
|
import { InboxPanel } from './components/InboxPanel';
|
|
11
11
|
import { LoadingSkeleton } from './components/LoadingSkeleton';
|
|
12
12
|
import { NodesEmptyState as EmptyState } from './components/EmptyState';
|
|
13
|
+
import { AliasAvatar } from './components/AliasAvatar';
|
|
14
|
+
import { STATUS_DOT_HEX, STATUS_CHIP_CLASS } from './lib/status';
|
|
13
15
|
import { UserBar } from './components/UserBar';
|
|
14
16
|
import { CommandCenter, useCommandCenter } from './components/CommandCenter';
|
|
15
17
|
import { DispatchPanel } from './components/DispatchPanel';
|
|
@@ -40,6 +42,7 @@ export default function Dashboard() {
|
|
|
40
42
|
const cmd = useCommandCenter();
|
|
41
43
|
const [showDispatch, setShowDispatch] = useState(false);
|
|
42
44
|
const [inbox, setInbox] = useState<InboxMessage[]>([]);
|
|
45
|
+
const [agentFilter, setAgentFilter] = useState<'all' | 'working' | 'idle' | 'offline'>('all');
|
|
43
46
|
const { mutate } = useSWRConfig();
|
|
44
47
|
|
|
45
48
|
// SSE: instant revalidation on CommHub events
|
|
@@ -120,7 +123,7 @@ export default function Dashboard() {
|
|
|
120
123
|
</div>
|
|
121
124
|
|
|
122
125
|
{/* Dispatch + User Bar */}
|
|
123
|
-
<div className="flex items-center gap-3 mb-
|
|
126
|
+
<div className="flex items-center gap-3 mb-4">
|
|
124
127
|
<button onClick={() => setShowDispatch(true)}
|
|
125
128
|
className="px-4 py-2 bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white text-sm font-medium rounded-xl shadow-lg shadow-cyan-500/10 transition-all active:scale-95 shrink-0">
|
|
126
129
|
⚡ Dispatch
|
|
@@ -189,9 +192,9 @@ export default function Dashboard() {
|
|
|
189
192
|
{/* Quick Actions — split into two distinct intents:
|
|
190
193
|
(1) Top: live stat cards (carry data, drill-in on click)
|
|
191
194
|
(2) Bottom: pure nav rail (no number, icon + label)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
Round 24 — wrap both in a labelled block so the rhythm reads as
|
|
196
|
+
"here are the main jumps" instead of two disconnected strips. */}
|
|
197
|
+
<div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 mb-2">Quick navigation</div>
|
|
195
198
|
<section className="mb-3 grid grid-cols-3 gap-2 sm:gap-3">
|
|
196
199
|
{(() => {
|
|
197
200
|
// Build breakdown popover content per card. Pure data — pure CSS
|
|
@@ -311,15 +314,18 @@ export default function Dashboard() {
|
|
|
311
314
|
<div className="space-y-2 max-h-40 overflow-y-auto">
|
|
312
315
|
{tasks.slice(0, 5).map((t: { task_id: string; from_name: string; to_name: string; status: string; content: string; created_at: string }) => (
|
|
313
316
|
<div key={t.task_id} className="flex items-center gap-2 text-xs">
|
|
314
|
-
<span
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
317
|
+
<span
|
|
318
|
+
className="w-1.5 h-1.5 rounded-full shrink-0"
|
|
319
|
+
style={{ backgroundColor: STATUS_DOT_HEX[t.status] || '#6b7280' }}
|
|
320
|
+
/>
|
|
321
|
+
{t.from_name && <AliasAvatar alias={t.from_name} size={14} />}
|
|
322
|
+
<span className="text-gray-300 shrink-0 max-w-[20%] truncate">{t.from_name || '?'}</span>
|
|
318
323
|
<span className="text-gray-600">→</span>
|
|
319
|
-
<
|
|
324
|
+
{t.to_name && <AliasAvatar alias={t.to_name} size={14} />}
|
|
325
|
+
<span className="text-gray-300 shrink-0 max-w-[20%] truncate">{t.to_name || '?'}</span>
|
|
320
326
|
<span className="text-gray-500 truncate flex-1">{t.content?.slice(0, 40) || '--'}</span>
|
|
321
327
|
<span className={`shrink-0 px-1.5 py-0.5 rounded text-[10px] border ${
|
|
322
|
-
t.status
|
|
328
|
+
STATUS_CHIP_CLASS[t.status] || 'text-gray-500 border-gray-700/30'
|
|
323
329
|
}`}>{t.status}</span>
|
|
324
330
|
</div>
|
|
325
331
|
))}
|
|
@@ -356,13 +362,67 @@ export default function Dashboard() {
|
|
|
356
362
|
|
|
357
363
|
{sessions.length === 0 && !sessError ? (
|
|
358
364
|
<EmptyState hint={sessHint} />
|
|
359
|
-
) : (
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
))
|
|
364
|
-
|
|
365
|
-
|
|
365
|
+
) : (() => {
|
|
366
|
+
const counts = {
|
|
367
|
+
all: sortedSessions.length,
|
|
368
|
+
working: sortedSessions.filter(s => isOnline(s) && s.status === 'working').length,
|
|
369
|
+
idle: sortedSessions.filter(s => isOnline(s) && s.status !== 'working').length,
|
|
370
|
+
offline: sortedSessions.filter(s => !isOnline(s)).length,
|
|
371
|
+
};
|
|
372
|
+
const filtered = sortedSessions.filter(s => {
|
|
373
|
+
if (agentFilter === 'all') return true;
|
|
374
|
+
if (agentFilter === 'offline') return !isOnline(s);
|
|
375
|
+
if (agentFilter === 'working') return isOnline(s) && s.status === 'working';
|
|
376
|
+
if (agentFilter === 'idle') return isOnline(s) && s.status !== 'working';
|
|
377
|
+
return true;
|
|
378
|
+
});
|
|
379
|
+
// Filter chip color keyed to status (round 34): working=green, idle=cyan,
|
|
380
|
+
// offline=gray, all=neutral. Inline hex dots avoid Tailwind v4 purge.
|
|
381
|
+
const chips: { key: typeof agentFilter; label: string; dot?: string }[] = [
|
|
382
|
+
{ key: 'all', label: 'All' },
|
|
383
|
+
{ key: 'working', label: 'Working', dot: '#4ade80' },
|
|
384
|
+
{ key: 'idle', label: 'Idle', dot: '#22d3ee' },
|
|
385
|
+
{ key: 'offline', label: 'Offline', dot: '#6b7280' },
|
|
386
|
+
];
|
|
387
|
+
return (
|
|
388
|
+
<>
|
|
389
|
+
<div className="mb-3 flex flex-wrap items-center gap-1">
|
|
390
|
+
{chips.map(c => (
|
|
391
|
+
<button
|
|
392
|
+
key={c.key}
|
|
393
|
+
type="button"
|
|
394
|
+
onClick={() => setAgentFilter(c.key)}
|
|
395
|
+
disabled={counts[c.key] === 0 && c.key !== 'all'}
|
|
396
|
+
className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors disabled:opacity-30 disabled:cursor-not-allowed ${
|
|
397
|
+
agentFilter === c.key
|
|
398
|
+
? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/30'
|
|
399
|
+
: 'text-gray-500 hover:text-gray-200 hover:bg-[#1a1a2a] border border-transparent'
|
|
400
|
+
}`}
|
|
401
|
+
>
|
|
402
|
+
{c.dot && <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full" style={{ backgroundColor: c.dot }} />}
|
|
403
|
+
<span>{c.label}</span>
|
|
404
|
+
<span className={`text-[10px] tabular-nums ${agentFilter === c.key ? 'text-cyan-400' : 'text-gray-600'}`}>{counts[c.key]}</span>
|
|
405
|
+
</button>
|
|
406
|
+
))}
|
|
407
|
+
</div>
|
|
408
|
+
{/* Round 48: previous breakpoints had `lg:grid-cols-2 xl:grid-cols-3`
|
|
409
|
+
which kept lg (1024-1279px) at only 2 columns even though
|
|
410
|
+
each AgentCard is fine ≥260px wide. With the sidebar (208px),
|
|
411
|
+
main area at lg is ~816px so 3 cols at ~272px each fits.
|
|
412
|
+
xl breakpoint auto-inherits lg=3 cols; 2xl bumps to 4. */}
|
|
413
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4">
|
|
414
|
+
{filtered.map(s => (
|
|
415
|
+
<AgentCard key={s.alias} session={s} hasSse={isOnline(s)} sseCount={sseLookup(s) || 0} onChat={cmd.openTab} />
|
|
416
|
+
))}
|
|
417
|
+
</div>
|
|
418
|
+
{filtered.length === 0 && (
|
|
419
|
+
<div className="mt-4 text-center text-xs text-gray-600">
|
|
420
|
+
No agents match "{agentFilter}" — <button onClick={() => setAgentFilter('all')} className="underline hover:text-gray-400">Show all</button>
|
|
421
|
+
</div>
|
|
422
|
+
)}
|
|
423
|
+
</>
|
|
424
|
+
);
|
|
425
|
+
})()}
|
|
366
426
|
|
|
367
427
|
<InboxPanel messages={inbox} />
|
|
368
428
|
|