@sleep2agi/agent-network-dashboard 0.4.5-preview.3 → 0.4.5-preview.31
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 +49 -49
- 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_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_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]__0jjqrcf._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0jjqrcf._.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_0dk5wao._.js +1 -1
- package/.next/server/chunks/ssr/app_0dk5wao._.js.map +1 -1
- 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_components_0lhp_k.._.js +3 -0
- package/.next/server/chunks/ssr/app_components_0lhp_k.._.js.map +1 -0
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +1 -1
- package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +1 -1
- package/.next/server/chunks/ssr/app_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/00-ce-5_uu76b.js +1 -0
- package/.next/static/chunks/05q0hb5.1ust6.js +7 -0
- package/.next/static/chunks/06x7w8nwggx~~.js +1 -0
- package/.next/static/chunks/08snd~vwkplkw.css +2 -0
- package/.next/static/chunks/0943n0t.9c-o2.js +1 -0
- package/.next/static/chunks/0b.hnp2_fa824.js +1 -0
- package/.next/static/chunks/{07okhs5561y5y.js → 0c1bp~g7.8ru..js} +1 -1
- package/.next/static/chunks/0csmku~3y472t.js +1 -0
- package/.next/static/chunks/0hqhvq6f83m38.js +1 -0
- package/.next/static/chunks/0ia72wyq50qmo.js +7 -0
- package/.next/static/chunks/0l3gf0ickq445.js +1 -0
- package/.next/static/chunks/0qnpc7rbxn12u.js +1 -0
- package/.next/static/chunks/121k.shryv1j_.js +1 -0
- package/.next/static/chunks/14r_.uh5d2r_e.js +1 -0
- package/.next/static/chunks/17uwnuk~0zoo3.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/CommandPalette.tsx +53 -20
- package/app/components/HelpOverlay.tsx +50 -10
- 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 +91 -24
- package/app/globals.css +129 -0
- package/app/login/page.tsx +37 -7
- package/app/logs/page.tsx +72 -21
- package/app/messages/page.tsx +85 -51
- package/app/node/page.tsx +32 -15
- package/app/nodes/page.tsx +21 -23
- package/app/page.tsx +66 -13
- package/app/server-logs/page.tsx +37 -3
- package/app/settings/networks/page.tsx +37 -13
- package/app/settings/page.tsx +33 -8
- package/app/tasks/page.tsx +44 -24
- 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_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_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/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/0lo_g.q1fm-hr.js +0 -7
- package/.next/static/chunks/0m328uz90rp81.js +0 -1
- package/.next/static/chunks/0ogeals5ufhho.js +0 -1
- package/.next/static/chunks/0qgc758gz3on4.js +0 -7
- package/.next/static/chunks/1031v6nw33ud3.js +0 -1
- package/.next/static/chunks/10njj7kn9ylkf.js +0 -1
- package/.next/static/chunks/11l7hps85s2k7.css +0 -2
- package/.next/static/chunks/13.dst7liziwo.js +0 -1
- /package/.next/static/{N_JUC5rLQduweE3JrL3yd → -jenqbbGDhHAGWzs7_tpO}/_buildManifest.js +0 -0
- /package/.next/static/{N_JUC5rLQduweE3JrL3yd → -jenqbbGDhHAGWzs7_tpO}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{N_JUC5rLQduweE3JrL3yd → -jenqbbGDhHAGWzs7_tpO}/_ssgManifest.js +0 -0
package/app/admin/page.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useSessions, useHealth, useStats } from '../lib/hooks';
|
|
5
5
|
import { timeAgo } from '../components/utils';
|
|
6
|
+
import { AliasAvatar } from '../components/AliasAvatar';
|
|
6
7
|
|
|
7
8
|
export default function AdminPage() {
|
|
8
9
|
const { sessions } = useSessions();
|
|
@@ -72,9 +73,32 @@ export default function AdminPage() {
|
|
|
72
73
|
|
|
73
74
|
return (
|
|
74
75
|
<div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
|
|
75
|
-
<h1 className="text-2xl font-bold text-white mb-
|
|
76
|
+
<h1 className="text-2xl font-bold text-white mb-3 lg:ml-0 ml-10">Admin</h1>
|
|
77
|
+
|
|
78
|
+
{/* Section anchor nav — same pattern as Settings r28. */}
|
|
79
|
+
<nav className="mb-8 flex flex-wrap gap-1 text-xs">
|
|
80
|
+
{[
|
|
81
|
+
{ href: '#status', label: 'Status' },
|
|
82
|
+
{ href: '#actions', label: 'Actions' },
|
|
83
|
+
{ href: '#users', label: 'Users' },
|
|
84
|
+
].map(a => (
|
|
85
|
+
<a key={a.href} href={a.href}
|
|
86
|
+
className="rounded-md px-2.5 py-1 text-gray-500 hover:text-cyan-300 hover:bg-cyan-500/10 transition-colors">
|
|
87
|
+
{a.label}
|
|
88
|
+
</a>
|
|
89
|
+
))}
|
|
90
|
+
</nav>
|
|
91
|
+
|
|
92
|
+
<div className="max-w-6xl space-y-10">
|
|
93
|
+
|
|
94
|
+
{/* ── Group: Status ─────────────────────────────────────── */}
|
|
95
|
+
<div id="status" className="space-y-4 scroll-mt-6">
|
|
96
|
+
<div className="flex items-center gap-2 px-1">
|
|
97
|
+
<div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Status</div>
|
|
98
|
+
<div className="flex-1 h-px bg-[#2a2a4a]" />
|
|
99
|
+
</div>
|
|
100
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
76
101
|
|
|
77
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 max-w-6xl">
|
|
78
102
|
{/* Server Overview */}
|
|
79
103
|
<section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
|
|
80
104
|
<h2 className="text-sm font-semibold text-gray-300 mb-4">Server Overview</h2>
|
|
@@ -107,6 +131,43 @@ export default function AdminPage() {
|
|
|
107
131
|
)}
|
|
108
132
|
</section>
|
|
109
133
|
|
|
134
|
+
{/* Online Sessions — pulled into Status group (was below Send Task) */}
|
|
135
|
+
<section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
|
|
136
|
+
<h2 className="text-sm font-semibold text-gray-300 mb-4">
|
|
137
|
+
Online Sessions <span className="text-gray-600">({onlineNodes.length})</span>
|
|
138
|
+
</h2>
|
|
139
|
+
<div className="space-y-2 max-h-80 overflow-y-auto">
|
|
140
|
+
{onlineNodes.length === 0 ? (
|
|
141
|
+
<div className="text-xs text-gray-600 text-center py-4">No online sessions</div>
|
|
142
|
+
) : onlineNodes.map(s => (
|
|
143
|
+
<div key={s.alias} className="flex items-center gap-3 bg-[#0a0a15] rounded-lg px-3 py-2 border border-[#1a1a2a]">
|
|
144
|
+
<AliasAvatar alias={s.alias} size={20} />
|
|
145
|
+
<div className="min-w-0 flex-1">
|
|
146
|
+
<div className="text-sm text-white font-medium truncate">{s.alias}</div>
|
|
147
|
+
<div className="text-xs text-gray-500 truncate">{s.agent || '--'} · {s.task || 'idle'}</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
150
|
+
<span className={`text-xs px-2 py-0.5 rounded border ${
|
|
151
|
+
s.status === 'working' ? 'bg-green-900/30 text-green-300 border-green-800/30' : 'bg-blue-900/30 text-blue-300 border-blue-800/30'
|
|
152
|
+
}`}>{s.status}</span>
|
|
153
|
+
<span className="text-[10px] text-gray-600">{timeAgo(s.updated_at)}</span>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
))}
|
|
157
|
+
</div>
|
|
158
|
+
</section>
|
|
159
|
+
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{/* ── Group: Actions ────────────────────────────────────── */}
|
|
164
|
+
<div id="actions" className="space-y-4 scroll-mt-6">
|
|
165
|
+
<div className="flex items-center gap-2 px-1">
|
|
166
|
+
<div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Actions</div>
|
|
167
|
+
<div className="flex-1 h-px bg-[#2a2a4a]" />
|
|
168
|
+
</div>
|
|
169
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
170
|
+
|
|
110
171
|
{/* Broadcast */}
|
|
111
172
|
<section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
|
|
112
173
|
<h2 className="text-sm font-semibold text-gray-300 mb-4">Broadcast</h2>
|
|
@@ -167,30 +228,17 @@ export default function AdminPage() {
|
|
|
167
228
|
{taskResult && <div className={`mt-2 text-xs ${taskResult.startsWith('Failed') ? 'text-red-400' : 'text-green-400'}`}>{taskResult}</div>}
|
|
168
229
|
</section>
|
|
169
230
|
|
|
170
|
-
{/* Online Sessions */}
|
|
171
|
-
<section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
|
|
172
|
-
<h2 className="text-sm font-semibold text-gray-300 mb-4">
|
|
173
|
-
Online Sessions <span className="text-gray-600">({onlineNodes.length})</span>
|
|
174
|
-
</h2>
|
|
175
|
-
<div className="space-y-2 max-h-80 overflow-y-auto">
|
|
176
|
-
{onlineNodes.length === 0 ? (
|
|
177
|
-
<div className="text-xs text-gray-600 text-center py-4">No online sessions</div>
|
|
178
|
-
) : onlineNodes.map(s => (
|
|
179
|
-
<div key={s.alias} className="flex items-center justify-between bg-[#0a0a15] rounded-lg px-3 py-2 border border-[#1a1a2a]">
|
|
180
|
-
<div className="min-w-0">
|
|
181
|
-
<div className="text-sm text-white font-medium truncate">{s.alias}</div>
|
|
182
|
-
<div className="text-xs text-gray-500 truncate">{s.agent || '--'} · {s.task || 'idle'}</div>
|
|
183
|
-
</div>
|
|
184
|
-
<div className="flex items-center gap-2 shrink-0">
|
|
185
|
-
<span className={`text-xs px-2 py-0.5 rounded border ${
|
|
186
|
-
s.status === 'working' ? 'bg-green-900/30 text-green-300 border-green-800/30' : 'bg-blue-900/30 text-blue-300 border-blue-800/30'
|
|
187
|
-
}`}>{s.status}</span>
|
|
188
|
-
<span className="text-[10px] text-gray-600">{timeAgo(s.updated_at)}</span>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
))}
|
|
192
231
|
</div>
|
|
193
|
-
</
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
{/* ── Group: Users ──────────────────────────────────────── */}
|
|
235
|
+
<div id="users" className="space-y-4 scroll-mt-6">
|
|
236
|
+
<div className="flex items-center gap-2 px-1">
|
|
237
|
+
<div className="text-[11px] uppercase tracking-[0.14em] text-gray-400 font-semibold">Users</div>
|
|
238
|
+
<div className="flex-1 h-px bg-[#2a2a4a]" />
|
|
239
|
+
</div>
|
|
240
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
241
|
+
|
|
194
242
|
{/* User Management (V3 Auth) */}
|
|
195
243
|
<section className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-5">
|
|
196
244
|
<h2 className="text-sm font-semibold text-gray-300 mb-4">Register User (V3)</h2>
|
|
@@ -219,6 +267,10 @@ export default function AdminPage() {
|
|
|
219
267
|
</div>
|
|
220
268
|
{regResult && <div className={`mt-2 text-xs ${regResult.startsWith('Failed') ? 'text-red-400' : 'text-green-400'}`}>{regResult}</div>}
|
|
221
269
|
</section>
|
|
270
|
+
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
222
274
|
</div>
|
|
223
275
|
</div>
|
|
224
276
|
);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import Link from 'next/link';
|
|
4
4
|
import { Session } from './types';
|
|
5
5
|
import { timeAgo } from './utils';
|
|
6
|
+
import { AliasAvatar } from './AliasAvatar';
|
|
6
7
|
|
|
7
8
|
interface AgentCardProps {
|
|
8
9
|
session: Session;
|
|
@@ -27,17 +28,20 @@ export function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardPro
|
|
|
27
28
|
<Link
|
|
28
29
|
href={`/node?alias=${encodeURIComponent(s.alias)}`}
|
|
29
30
|
prefetch={false}
|
|
30
|
-
className={`block rounded-xl border p-4 transition-all duration-300 cursor-pointer ${
|
|
31
|
+
className={`anet-agent-card group relative block rounded-xl border p-4 transition-all duration-300 cursor-pointer hover:-translate-y-0.5 ${
|
|
31
32
|
hasSse
|
|
32
33
|
? `bg-[#111128] border-[#2a2a4a] hover:border-cyan-500/30 hover:shadow-lg ${cfg.glow}`
|
|
33
34
|
: 'bg-[#0d0d1a] border-[#1a1a2a] opacity-40'
|
|
34
35
|
}`}
|
|
35
36
|
>
|
|
36
|
-
{/* Header: name + status
|
|
37
|
+
{/* Header: avatar + name + status. Avatar carries the alias→hue map
|
|
38
|
+
shared with Messages/Nodes/Tasks/Overview; the live status dot
|
|
39
|
+
stays as a small pulse-capable indicator. */}
|
|
37
40
|
<div className="flex items-center justify-between mb-3">
|
|
38
41
|
<div className="flex items-center gap-2 min-w-0">
|
|
39
|
-
<
|
|
42
|
+
<AliasAvatar alias={s.alias} size={22} />
|
|
40
43
|
<span className="font-semibold text-white truncate text-sm" title={s.alias}>{s.alias}</span>
|
|
44
|
+
<span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${cfg.dot} ${hasSse && s.status === 'working' ? 'animate-pulse' : ''}`} />
|
|
41
45
|
</div>
|
|
42
46
|
<span className={`text-[11px] px-2 py-0.5 rounded-md border shrink-0 ${cfg.bg} ${cfg.text}`}>
|
|
43
47
|
{hasSse ? s.status : 'offline'}
|
|
@@ -79,9 +83,12 @@ export function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardPro
|
|
|
79
83
|
</div>
|
|
80
84
|
)}
|
|
81
85
|
|
|
82
|
-
{/* Footer: time + chat
|
|
86
|
+
{/* Footer: time + chat + hover chevron affordance (round 44).
|
|
87
|
+
The card is a <Link> so it's clickable everywhere, but with no
|
|
88
|
+
visible cue users may not realise. Chevron appears on hover and
|
|
89
|
+
slides right ~2px for a "drill in" hint. */}
|
|
83
90
|
<div className="mt-3 flex justify-between items-center text-[10px] text-gray-600">
|
|
84
|
-
<span>{s.server || '--'}</span>
|
|
91
|
+
<span className="truncate" title={s.server || ''}>{s.server || '--'}</span>
|
|
85
92
|
<div className="flex items-center gap-2">
|
|
86
93
|
{onChat && hasSse && (
|
|
87
94
|
<button
|
|
@@ -92,6 +99,13 @@ export function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardPro
|
|
|
92
99
|
</button>
|
|
93
100
|
)}
|
|
94
101
|
<span>{timeAgo(s.updated_at)}</span>
|
|
102
|
+
<svg
|
|
103
|
+
aria-hidden
|
|
104
|
+
className="w-3 h-3 text-gray-700 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200"
|
|
105
|
+
fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
|
106
|
+
>
|
|
107
|
+
<path d="M9 18l6-6-6-6" />
|
|
108
|
+
</svg>
|
|
95
109
|
</div>
|
|
96
110
|
</div>
|
|
97
111
|
</Link>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Hue-hashed avatar pill for agent aliases. Same alias → same color across
|
|
2
|
+
* every page (Messages, Nodes, TopoGraph). Use the `size` prop for inline
|
|
3
|
+
* pills (16/20) vs card headers (28). Round 21 introduced the palette;
|
|
4
|
+
* round 22 promotes it from app/messages to a shared component. */
|
|
5
|
+
|
|
6
|
+
const AVATAR_HUES = [180, 200, 220, 270, 300, 330, 30, 90];
|
|
7
|
+
|
|
8
|
+
export function aliasAvatarColors(alias: string): { bg: string; ring: string; text: string } {
|
|
9
|
+
let h = 0;
|
|
10
|
+
for (let i = 0; i < alias.length; i++) h = (h * 31 + alias.charCodeAt(i)) >>> 0;
|
|
11
|
+
const hue = AVATAR_HUES[h % AVATAR_HUES.length];
|
|
12
|
+
return {
|
|
13
|
+
bg: `hsl(${hue} 55% 22%)`,
|
|
14
|
+
ring: `hsl(${hue} 60% 45%)`,
|
|
15
|
+
text: `hsl(${hue} 80% 78%)`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function aliasInitial(alias?: string): string {
|
|
20
|
+
if (!alias) return '·';
|
|
21
|
+
const ch = alias.trim().match(/[\p{L}\p{N}]/u)?.[0] || alias.trim()[0] || '·';
|
|
22
|
+
return ch.toUpperCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface AliasAvatarProps {
|
|
26
|
+
alias: string;
|
|
27
|
+
size?: number;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function AliasAvatar({ alias, size = 28, className = '' }: AliasAvatarProps) {
|
|
32
|
+
const c = aliasAvatarColors(alias);
|
|
33
|
+
const fs = Math.max(9, Math.round(size * 0.42));
|
|
34
|
+
return (
|
|
35
|
+
<span
|
|
36
|
+
className={`anet-alias-avatar inline-flex items-center justify-center rounded-full border shrink-0 font-semibold ${className}`}
|
|
37
|
+
style={{
|
|
38
|
+
width: size,
|
|
39
|
+
height: size,
|
|
40
|
+
fontSize: fs,
|
|
41
|
+
backgroundColor: c.bg,
|
|
42
|
+
borderColor: c.ring,
|
|
43
|
+
color: c.text,
|
|
44
|
+
}}
|
|
45
|
+
title={alias}
|
|
46
|
+
aria-hidden
|
|
47
|
+
>
|
|
48
|
+
{aliasInitial(alias)}
|
|
49
|
+
</span>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -22,6 +22,9 @@ interface Command {
|
|
|
22
22
|
group: 'Recents' | 'Agents' | 'Tasks' | 'Actions' | 'Navigate';
|
|
23
23
|
icon: React.ReactNode;
|
|
24
24
|
perform: (router: ReturnType<typeof useRouter>) => void;
|
|
25
|
+
/** Matched char indices (set by fuzzy filter for highlight render). */
|
|
26
|
+
_titleHi?: number[];
|
|
27
|
+
_hintHi?: number[];
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
interface SessionRow { alias?: string; status?: string; agent?: string }
|
|
@@ -46,19 +49,21 @@ function pushRecent(id: string) {
|
|
|
46
49
|
} catch {}
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
/** Fuzzy match: returns
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
/** Fuzzy match: returns score + matched char positions in `hay`. -1 score
|
|
53
|
+
* means no match. Round 18 introduced fuzzy; round 20 returns indices so
|
|
54
|
+
* the UI can highlight matched chars (Linear / Raycast style). */
|
|
55
|
+
interface FuzzyResult { score: number; indices: number[]; }
|
|
56
|
+
function fuzzyMatch(hay: string, needle: string): FuzzyResult {
|
|
57
|
+
if (!needle) return { score: 0, indices: [] };
|
|
54
58
|
const H = hay.toLowerCase();
|
|
55
59
|
const N = needle.toLowerCase();
|
|
56
60
|
let hi = 0, ni = 0, score = 0, streak = 0;
|
|
61
|
+
const indices: number[] = [];
|
|
57
62
|
while (hi < H.length && ni < N.length) {
|
|
58
63
|
if (H[hi] === N[ni]) {
|
|
59
|
-
score += 2 + streak;
|
|
64
|
+
score += 2 + streak;
|
|
60
65
|
streak++;
|
|
61
|
-
|
|
66
|
+
indices.push(hi);
|
|
62
67
|
if (hi === 0 || /[\s·:_/-]/.test(H[hi - 1])) score += 4;
|
|
63
68
|
ni++;
|
|
64
69
|
} else {
|
|
@@ -66,9 +71,25 @@ function fuzzyScore(hay: string, needle: string): number {
|
|
|
66
71
|
}
|
|
67
72
|
hi++;
|
|
68
73
|
}
|
|
69
|
-
if (ni < N.length) return -1
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
if (ni < N.length) return { score: -1, indices: [] };
|
|
75
|
+
return { score: score - Math.max(0, H.length - N.length) * 0.05, indices };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Renders `text` with the chars at `indices` wrapped in <mark>. Handles
|
|
79
|
+
* unicode (Chinese aliases, emoji) by iterating code points via Array.from. */
|
|
80
|
+
function Highlight({ text, indices }: { text: string; indices: number[] }) {
|
|
81
|
+
if (!indices.length) return <>{text}</>;
|
|
82
|
+
const set = new Set(indices);
|
|
83
|
+
const chars = Array.from(text);
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
{chars.map((ch, i) =>
|
|
87
|
+
set.has(i)
|
|
88
|
+
? <mark key={i} className="anet-cmdk-mark">{ch}</mark>
|
|
89
|
+
: <span key={i}>{ch}</span>
|
|
90
|
+
)}
|
|
91
|
+
</>
|
|
92
|
+
);
|
|
72
93
|
}
|
|
73
94
|
|
|
74
95
|
function NavIcon({ d }: { d: string }) {
|
|
@@ -237,21 +258,27 @@ export function CommandPalette() {
|
|
|
237
258
|
}
|
|
238
259
|
// Fuzzy match — `stg` matches `Settings`, `taska` matches `Task all-time`.
|
|
239
260
|
// Score against title (weight 1.0), hint (0.6), id (0.3). Drop unmatched.
|
|
261
|
+
// Round 20: keep matched indices for title + hint so we can highlight.
|
|
240
262
|
const scored = pool
|
|
241
263
|
.map(c => {
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
const
|
|
264
|
+
const mt = fuzzyMatch(c.title, query);
|
|
265
|
+
const mh = c.hint ? fuzzyMatch(c.hint, query) : { score: -1, indices: [] };
|
|
266
|
+
const mi = fuzzyMatch(c.id, query);
|
|
245
267
|
const best = Math.max(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
268
|
+
mt.score >= 0 ? mt.score * 1.0 : -Infinity,
|
|
269
|
+
mh.score >= 0 ? mh.score * 0.6 : -Infinity,
|
|
270
|
+
mi.score >= 0 ? mi.score * 0.3 : -Infinity,
|
|
249
271
|
);
|
|
250
|
-
return {
|
|
272
|
+
return {
|
|
273
|
+
c,
|
|
274
|
+
score: best,
|
|
275
|
+
_titleHi: mt.score >= 0 ? mt.indices : [],
|
|
276
|
+
_hintHi: mh.score >= 0 ? mh.indices : [],
|
|
277
|
+
};
|
|
251
278
|
})
|
|
252
279
|
.filter(x => x.score > -Infinity);
|
|
253
280
|
scored.sort((a, b) => b.score - a.score);
|
|
254
|
-
return scored.map(x => x.c);
|
|
281
|
+
return scored.map(x => ({ ...x.c, _titleHi: x._titleHi, _hintHi: x._hintHi }));
|
|
255
282
|
}, [query, recentIds, dynamic]);
|
|
256
283
|
|
|
257
284
|
// Clamp selected when results shrink
|
|
@@ -332,8 +359,14 @@ export function CommandPalette() {
|
|
|
332
359
|
>
|
|
333
360
|
<span className={isActive ? 'text-cyan-400' : 'text-gray-500'}>{c.icon}</span>
|
|
334
361
|
<span className="flex-1 min-w-0">
|
|
335
|
-
<span className="block truncate">
|
|
336
|
-
|
|
362
|
+
<span className="block truncate">
|
|
363
|
+
<Highlight text={c.title} indices={c._titleHi || []} />
|
|
364
|
+
</span>
|
|
365
|
+
{c.hint && (
|
|
366
|
+
<span className="block text-[11px] text-gray-600 truncate">
|
|
367
|
+
<Highlight text={c.hint} indices={c._hintHi || []} />
|
|
368
|
+
</span>
|
|
369
|
+
)}
|
|
337
370
|
</span>
|
|
338
371
|
{isActive && <span className="text-[10px] text-gray-500">↵</span>}
|
|
339
372
|
</button>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useEffect, useState, useRef } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
4
5
|
|
|
5
6
|
/** Help overlay — press `?` to see keyboard shortcuts.
|
|
6
7
|
* Restrained: no animation noise, single column, all global keys in one
|
|
@@ -23,29 +24,52 @@ const SHORTCUTS: { group: string; items: Shortcut[] }[] = [
|
|
|
23
24
|
],
|
|
24
25
|
},
|
|
25
26
|
{
|
|
26
|
-
group: '
|
|
27
|
+
group: 'Navigate (vim-style)',
|
|
27
28
|
items: [
|
|
28
|
-
{ keys: ['
|
|
29
|
-
{ keys: ['
|
|
30
|
-
{ keys: ['
|
|
29
|
+
{ keys: ['g', 'o'], label: 'Go to Overview' },
|
|
30
|
+
{ keys: ['g', 't'], label: 'Go to Tasks' },
|
|
31
|
+
{ keys: ['g', 'n'], label: 'Go to Nodes' },
|
|
32
|
+
{ keys: ['g', 'm'], label: 'Go to Messages' },
|
|
33
|
+
{ keys: ['g', 'w'], label: 'Go to Networks' },
|
|
34
|
+
{ keys: ['g', 'a'], label: 'Go to Admin' },
|
|
35
|
+
{ keys: ['g', 'l'], label: 'Go to Audit Log' },
|
|
36
|
+
{ keys: ['g', 's'], label: 'Go to Settings' },
|
|
31
37
|
],
|
|
32
38
|
},
|
|
33
39
|
{
|
|
34
|
-
group: '
|
|
40
|
+
group: 'Command palette',
|
|
35
41
|
items: [
|
|
36
|
-
{ keys: ['
|
|
37
|
-
{ keys: ['
|
|
42
|
+
{ keys: ['↑', '↓'], label: 'Move selection' },
|
|
43
|
+
{ keys: ['↵'], label: 'Activate selected command' },
|
|
44
|
+
{ keys: ['type'], label: 'Filter — searches commands, agents, tasks' },
|
|
38
45
|
],
|
|
39
46
|
},
|
|
40
47
|
];
|
|
41
48
|
|
|
49
|
+
const GO_TARGETS: Record<string, string> = {
|
|
50
|
+
o: '/',
|
|
51
|
+
t: '/tasks',
|
|
52
|
+
n: '/nodes',
|
|
53
|
+
m: '/messages',
|
|
54
|
+
w: '/settings/networks',
|
|
55
|
+
a: '/admin',
|
|
56
|
+
l: '/logs',
|
|
57
|
+
s: '/settings',
|
|
58
|
+
};
|
|
59
|
+
|
|
42
60
|
export function HelpOverlay() {
|
|
43
61
|
const [open, setOpen] = useState(false);
|
|
62
|
+
const router = useRouter();
|
|
63
|
+
/** Timestamp when `g` was last pressed. The next single letter within
|
|
64
|
+
* 1500ms is interpreted as the target page. vim-style `g o`, `g t`, etc. */
|
|
65
|
+
const gAtRef = useRef<number>(0);
|
|
44
66
|
|
|
45
67
|
useEffect(() => {
|
|
46
68
|
const onKey = (e: KeyboardEvent) => {
|
|
69
|
+
const tag = document.activeElement?.tagName;
|
|
70
|
+
const inInput = tag === 'INPUT' || tag === 'TEXTAREA' || (document.activeElement as HTMLElement | null)?.isContentEditable;
|
|
47
71
|
// `?` toggles — Shift+/ on US layout. Only when no input is focused.
|
|
48
|
-
if (e.key === '?' &&
|
|
72
|
+
if (e.key === '?' && !inInput) {
|
|
49
73
|
e.preventDefault();
|
|
50
74
|
setOpen(prev => !prev);
|
|
51
75
|
return;
|
|
@@ -53,10 +77,26 @@ export function HelpOverlay() {
|
|
|
53
77
|
if (e.key === 'Escape' && open) {
|
|
54
78
|
setOpen(false);
|
|
55
79
|
}
|
|
80
|
+
// Vim-style `g + letter` route shortcuts (round 35). Skip when modifier
|
|
81
|
+
// keys are held (g is a perfectly fine letter when ⌘ is involved) and
|
|
82
|
+
// when typing into a field.
|
|
83
|
+
if (inInput || e.metaKey || e.ctrlKey || e.altKey) return;
|
|
84
|
+
if (e.key === 'g') {
|
|
85
|
+
gAtRef.current = Date.now();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (Date.now() - gAtRef.current < 1500) {
|
|
89
|
+
const target = GO_TARGETS[e.key.toLowerCase()];
|
|
90
|
+
if (target) {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
gAtRef.current = 0;
|
|
93
|
+
router.push(target);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
56
96
|
};
|
|
57
97
|
window.addEventListener('keydown', onKey);
|
|
58
98
|
return () => window.removeEventListener('keydown', onKey);
|
|
59
|
-
}, [open]);
|
|
99
|
+
}, [open, router]);
|
|
60
100
|
|
|
61
101
|
if (!open) return null;
|
|
62
102
|
|
|
@@ -51,11 +51,12 @@ export function Sidebar() {
|
|
|
51
51
|
href={item.href}
|
|
52
52
|
prefetch={false}
|
|
53
53
|
onClick={() => setMobileOpen(false)}
|
|
54
|
+
title={collapsed ? item.label : undefined}
|
|
54
55
|
className={`relative flex items-center gap-3 px-3 py-3 rounded-lg text-sm transition-colors active:bg-[#1a1a3a] ${
|
|
55
56
|
isActive(item.href)
|
|
56
57
|
? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/20 anet-nav-active'
|
|
57
58
|
: 'text-gray-400 hover:text-gray-200 hover:bg-[#1a1a2a]'
|
|
58
|
-
} ${collapsed ? 'justify-center' : ''}`}
|
|
59
|
+
} ${collapsed ? 'justify-center px-0' : ''}`}
|
|
59
60
|
>
|
|
60
61
|
<svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
61
62
|
<path strokeLinecap="round" strokeLinejoin="round" d={item.icon} />
|
|
@@ -81,10 +82,11 @@ export function Sidebar() {
|
|
|
81
82
|
</svg>
|
|
82
83
|
</button>
|
|
83
84
|
|
|
84
|
-
{/* Mobile overlay
|
|
85
|
+
{/* Mobile overlay — round 47: fades in via anet-fade-in (150ms ease-out)
|
|
86
|
+
matching the TaskDrawer / TaskChatPanel backdrop. */}
|
|
85
87
|
{mobileOpen && (
|
|
86
88
|
<div
|
|
87
|
-
className="fixed inset-0 bg-black/50 z-30 lg:hidden"
|
|
89
|
+
className="fixed inset-0 bg-black/50 z-30 lg:hidden anet-fade-in"
|
|
88
90
|
onClick={() => setMobileOpen(false)}
|
|
89
91
|
/>
|
|
90
92
|
)}
|
|
@@ -93,13 +95,15 @@ export function Sidebar() {
|
|
|
93
95
|
but in light/mint we want a distinct surface, so we layer
|
|
94
96
|
`lg:bg-white` / `lg:dark:bg-[#0d0d1a]` via the theme attribute.
|
|
95
97
|
The CSS shim in globals.css upgrades sidebar bg to bg-secondary
|
|
96
|
-
in light themes so the sidebar reads as its own card.
|
|
98
|
+
in light themes so the sidebar reads as its own card.
|
|
99
|
+
Round 47: explicit ease-out curve + slight shadow so the drawer
|
|
100
|
+
edge "leaves a trail" as it slides in. */}
|
|
97
101
|
<aside data-anet-sidebar="true" className={`
|
|
98
102
|
fixed top-0 left-0 h-full z-40 bg-[#0d0d1a] border-r border-[#2a2a4a]
|
|
99
|
-
transition-
|
|
103
|
+
transition-transform duration-200 ease-out
|
|
100
104
|
${collapsed ? 'w-16' : 'w-52'}
|
|
101
|
-
${mobileOpen ? 'translate-x-0' : '-translate-x-full'}
|
|
102
|
-
lg:translate-x-0 lg:static lg:shrink-0
|
|
105
|
+
${mobileOpen ? 'translate-x-0 shadow-2xl shadow-black/40 lg:shadow-none' : '-translate-x-full'}
|
|
106
|
+
lg:translate-x-0 lg:static lg:shrink-0 lg:shadow-none
|
|
103
107
|
`}>
|
|
104
108
|
{/* Brand header — round 4: 3-node mesh mark matches /login,
|
|
105
109
|
with an inline live "online" pulse so every page surfaces
|
|
@@ -134,45 +138,49 @@ export function Sidebar() {
|
|
|
134
138
|
{nav}
|
|
135
139
|
</div>
|
|
136
140
|
|
|
137
|
-
{/* Sign out + collapse
|
|
141
|
+
{/* Sign out + collapse — round 27: collapsed-state gets icon-only
|
|
142
|
+
variants so users still have Sign out / Quick search access at
|
|
143
|
+
56px width, plus title= tooltips. */}
|
|
138
144
|
<div className="absolute bottom-0 left-0 right-0 border-t border-[#2a2a4a] bg-[#0d0d1a]">
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
<button
|
|
146
|
+
onClick={() => {
|
|
147
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true, ctrlKey: true }));
|
|
148
|
+
}}
|
|
149
|
+
title={collapsed ? 'Quick search (⌘K)' : undefined}
|
|
150
|
+
className={`w-full flex items-center text-[11px] text-gray-600 hover:text-gray-400 hover:bg-[#1a1a2a] transition-colors ${
|
|
151
|
+
collapsed ? 'justify-center px-0 py-2.5' : 'justify-between gap-2 px-5 py-2'
|
|
152
|
+
}`}
|
|
153
|
+
aria-label="Open command palette"
|
|
154
|
+
>
|
|
155
|
+
<span className={`flex items-center ${collapsed ? '' : 'gap-2'}`}>
|
|
156
|
+
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
157
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
150
158
|
</svg>
|
|
151
|
-
|
|
152
|
-
</
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<div className={`flex items-center gap-2 px-3 py-2 ${collapsed ? 'justify-center' : 'justify-between'}`}>
|
|
159
|
+
{!collapsed && 'Quick search'}
|
|
160
|
+
</span>
|
|
161
|
+
{!collapsed && <kbd className="text-[10px] border border-current rounded px-1 py-0.5 opacity-60 font-mono">⌘K</kbd>}
|
|
162
|
+
</button>
|
|
163
|
+
<button
|
|
164
|
+
onClick={async () => {
|
|
165
|
+
await fetch('/api/auth/logout', { method: 'POST' }).catch(() => {});
|
|
166
|
+
sessionStorage.removeItem('anet_v3_auth');
|
|
167
|
+
window.location.assign('/login');
|
|
168
|
+
}}
|
|
169
|
+
title={collapsed ? 'Sign out' : undefined}
|
|
170
|
+
className={`w-full flex items-center text-xs text-gray-500 hover:text-red-400 hover:bg-red-500/5 transition-colors ${
|
|
171
|
+
collapsed ? 'justify-center px-0 py-2.5' : 'gap-3 px-5 py-3'
|
|
172
|
+
}`}
|
|
173
|
+
>
|
|
174
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
175
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
|
|
176
|
+
</svg>
|
|
177
|
+
{!collapsed && 'Sign out'}
|
|
178
|
+
</button>
|
|
179
|
+
<div className={`flex items-center gap-2 ${collapsed ? 'flex-col px-0 py-2' : 'px-3 py-2 justify-between'}`}>
|
|
173
180
|
<ThemeSwitcher compact={collapsed} />
|
|
174
181
|
<button
|
|
175
182
|
onClick={() => setCollapsed(!collapsed)}
|
|
183
|
+
title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
176
184
|
className="hidden lg:flex p-1.5 rounded text-gray-600 hover:text-gray-400 transition-colors"
|
|
177
185
|
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
178
186
|
>
|