@sleep2agi/agent-network-dashboard 0.4.6-preview.8 → 0.4.6
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/app-path-routes-manifest.json +1 -0
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +69 -55
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +6 -0
- 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 +2 -2
- package/.next/server/app/_not-found.rsc +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/servers/route.js +7 -0
- package/.next/server/app/api/hub/servers/route.js.map +5 -0
- package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +14 -14
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_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_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +14 -14
- package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +14 -14
- package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +14 -14
- package/.next/server/app/node.segments/_full.segment.rsc +14 -14
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +14 -14
- package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- package/.next/server/app/page_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 +2 -2
- package/.next/server/app/server-logs.rsc +14 -14
- package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings/page_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 +2 -2
- package/.next/server/app/settings/tokens.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +14 -14
- package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/app-paths-manifest.json +1 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +2 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
- package/.next/static/chunks/01lmbsd37fybu.js +1 -0
- package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
- package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
- package/.next/static/chunks/05uk96gc~9mni.js +1 -0
- package/.next/static/chunks/088ouqy7tekty.js +2 -0
- package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
- package/.next/static/chunks/0glmrdj9112tc.js +7 -0
- package/.next/static/chunks/0hyl7mdapsz38.js +1 -0
- package/.next/static/chunks/0mqwn-l7wtcdo.js +7 -0
- package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
- package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
- package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
- package/.next/static/chunks/127kw7~wsnyu7.css +2 -0
- package/.next/static/chunks/16yfxp9ak2gdm.js +1 -0
- package/.next/static/chunks/174re5ar~kxne.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/app/api/hub/servers/route.ts +39 -0
- package/app/components/AppShell.tsx +2 -0
- package/app/components/ChatPopover.tsx +38 -2
- package/app/components/HelpOverlay.tsx +18 -0
- package/app/components/ServersDrawer.tsx +206 -0
- package/app/components/TopoGraph.tsx +871 -162
- package/app/components/utils.ts +8 -7
- package/app/lib/time.ts +36 -0
- package/package.json +1 -1
- package/scripts/help-overlay-topo-test.mjs +44 -0
- package/scripts/servers-drawer-live-test.mjs +96 -0
- package/scripts/topo-active-links-chip-test.mjs +73 -0
- package/scripts/topo-arrow-tier-test.mjs +61 -0
- package/scripts/topo-auto-fit-test.mjs +73 -0
- package/scripts/topo-chat-focus-ring-test.mjs +77 -0
- package/scripts/topo-click-ripple-test.mjs +66 -0
- package/scripts/topo-cwd-tooltip-test.mjs +64 -0
- package/scripts/topo-dblclick-reset-test.mjs +74 -0
- package/scripts/topo-edge-hover-test.mjs +85 -0
- package/scripts/topo-edge-tooltip-test.mjs +70 -0
- package/scripts/topo-fit-key-test.mjs +106 -0
- package/scripts/topo-freshness-chip-test.mjs +74 -0
- package/scripts/topo-group-count-test.mjs +63 -0
- package/scripts/topo-group-hover-test.mjs +82 -0
- package/scripts/topo-hub-title-test.mjs +88 -0
- package/scripts/topo-label-lift-test.mjs +115 -0
- package/scripts/topo-label-scale-test.mjs +78 -0
- package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
- package/scripts/topo-layout-key-test.mjs +91 -0
- package/scripts/topo-minimap-test.mjs +68 -0
- package/scripts/topo-mobile-chips-test.mjs +103 -0
- package/scripts/topo-offline-dim-test.mjs +70 -0
- package/scripts/topo-overlap-test.mjs +164 -0
- package/scripts/topo-p0round-test.mjs +2 -2
- package/scripts/topo-pan-cursor-test.mjs +62 -0
- package/scripts/topo-popover-meta-test.mjs +78 -0
- package/scripts/topo-pressure-bar-test.mjs +69 -0
- package/scripts/topo-pulse-rate-test.mjs +71 -0
- package/scripts/topo-recent-signal-empty-test.mjs +63 -0
- package/scripts/topo-reduced-motion-test.mjs +77 -0
- package/scripts/topo-tz-ghost-test.mjs +69 -0
- package/scripts/topo-wheel-zoom-test.mjs +79 -0
- package/scripts/topo-zoom-keys-test.mjs +89 -0
- package/.next/static/chunks/0-7iowzu4.798.js +0 -1
- package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
- package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
- package/.next/static/chunks/0_rmxom9~1lmk.css +0 -2
- package/.next/static/chunks/0cbezu61m-e~z.js +0 -1
- package/.next/static/chunks/0cj5qr_a8dzky.js +0 -7
- package/.next/static/chunks/0elalb9n_.~di.js +0 -1
- package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
- package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
- package/.next/static/chunks/0mn0ruf0g4op5.js +0 -1
- package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
- package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
- package/.next/static/chunks/0y9subfnntv1s.js +0 -1
- package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
- package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
- /package/.next/static/{7xem03-473G3qw-fC160p → 23MxP0RBVA4BaOJ_HMYmM}/_buildManifest.js +0 -0
- /package/.next/static/{7xem03-473G3qw-fC160p → 23MxP0RBVA4BaOJ_HMYmM}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{7xem03-473G3qw-fC160p → 23MxP0RBVA4BaOJ_HMYmM}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { requireDashboardAuth, getV3UserToken } from '@/app/lib/dashboard-auth';
|
|
2
|
+
|
|
3
|
+
const HUB_URL = process.env.COMMHUB_URL || 'http://127.0.0.1:9200';
|
|
4
|
+
|
|
5
|
+
async function hubHeaders(): Promise<Record<string, string>> {
|
|
6
|
+
const userToken = await getV3UserToken();
|
|
7
|
+
const h: Record<string, string> = {};
|
|
8
|
+
if (userToken) h['Authorization'] = `Bearer ${userToken}`;
|
|
9
|
+
return h;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Proxy: GET /api/servers from CommHub. Aggregates host telemetry across
|
|
14
|
+
* sessions and returns one card per server (hostname, ip, cpu/mem load,
|
|
15
|
+
* agent_count, status). Per issue #119; lands in v0.8.1-preview.2 of the
|
|
16
|
+
* commhub-server (通信牛). The dashboard <ServersDrawer> mounts in
|
|
17
|
+
* AppShell and refreshes every 5s when expanded.
|
|
18
|
+
*/
|
|
19
|
+
export async function GET() {
|
|
20
|
+
const authFailure = await requireDashboardAuth();
|
|
21
|
+
if (authFailure) return authFailure;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(`${HUB_URL}/api/servers`, {
|
|
25
|
+
headers: await hubHeaders(),
|
|
26
|
+
next: { revalidate: 0 },
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
// 404 means the upstream hub predates v0.8.1-preview.2 — surface a
|
|
30
|
+
// 'feature unavailable' shape so the drawer can render a friendly
|
|
31
|
+
// empty state instead of an angry error.
|
|
32
|
+
if (res.status === 404) return Response.json({ servers: [], unavailable: true });
|
|
33
|
+
return Response.json({ error: `hub ${res.status}` }, { status: res.status });
|
|
34
|
+
}
|
|
35
|
+
return Response.json(await res.json());
|
|
36
|
+
} catch (e: unknown) {
|
|
37
|
+
return Response.json({ error: 'hub unreachable', detail: e instanceof Error ? e.message : String(e) }, { status: 502 });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -5,6 +5,7 @@ import { Sidebar } from './Sidebar';
|
|
|
5
5
|
import { HealthBanner } from './HealthBanner';
|
|
6
6
|
import { CommandPalette } from './CommandPalette';
|
|
7
7
|
import { HelpOverlay } from './HelpOverlay';
|
|
8
|
+
import { ServersDrawer } from './ServersDrawer';
|
|
8
9
|
|
|
9
10
|
const NO_SHELL_PATHS = ['/login'];
|
|
10
11
|
|
|
@@ -24,6 +25,7 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
|
|
24
25
|
</main>
|
|
25
26
|
<CommandPalette />
|
|
26
27
|
<HelpOverlay />
|
|
28
|
+
<ServersDrawer />
|
|
27
29
|
</div>
|
|
28
30
|
);
|
|
29
31
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { AliasAvatar } from './AliasAvatar';
|
|
5
5
|
import { TaskChatPanel } from './TaskChatPanel';
|
|
6
|
+
import { useSessions } from '../lib/hooks';
|
|
7
|
+
import { relativeAgo } from '../lib/time';
|
|
6
8
|
|
|
7
9
|
interface ChatPopoverProps {
|
|
8
10
|
/** Node alias to chat with. Changing it switches the conversation. */
|
|
@@ -37,6 +39,16 @@ const MOBILE_BP = 640;
|
|
|
37
39
|
* history are already solved there; this component only adds the floating
|
|
38
40
|
* shell + drag/resize behaviour.
|
|
39
41
|
*/
|
|
42
|
+
/** Round 37 / Loop: surface node metadata (cwd + last-seen) inside the
|
|
43
|
+
* ChatPopover header. The SVG <title> tooltip (Rounds 33-34) only shows
|
|
44
|
+
* on hover-over-node, which is lost once the popover is open and
|
|
45
|
+
* potentially dragged away. Lifting cwd and last-seen into the popover
|
|
46
|
+
* header keeps the context where the user actually needs it.
|
|
47
|
+
*
|
|
48
|
+
* Round 38: relativeAgo factored to app/lib/time.ts so this file shares
|
|
49
|
+
* the same TZ-safe parser as TopoGraph (was a duplicated mirror until
|
|
50
|
+
* this round). */
|
|
51
|
+
|
|
40
52
|
export function ChatPopover({ alias, onClose }: ChatPopoverProps) {
|
|
41
53
|
// Position + size are resolved on mount (SSR-safe defaults here).
|
|
42
54
|
const [pos, setPos] = useState({ x: 0, y: 0 });
|
|
@@ -48,6 +60,13 @@ export function ChatPopover({ alias, onClose }: ChatPopoverProps) {
|
|
|
48
60
|
active: false, startX: 0, startY: 0, baseW: 0, baseH: 0,
|
|
49
61
|
});
|
|
50
62
|
|
|
63
|
+
// Round 37 / Loop: pull the target session out of the SWR cache that
|
|
64
|
+
// TopoGraph already populates — same URL, so no extra network fetch.
|
|
65
|
+
const { sessions } = useSessions();
|
|
66
|
+
const session = useMemo(() => sessions.find(s => s.alias === alias), [sessions, alias]);
|
|
67
|
+
const isOnline = !!session && session.status !== 'offline';
|
|
68
|
+
const lastSeenLine = !isOnline ? relativeAgo(session?.last_seen_at) : null;
|
|
69
|
+
|
|
51
70
|
const clamp = useCallback((x: number, y: number, w: number, h: number) => {
|
|
52
71
|
const maxX = Math.max(MARGIN, window.innerWidth - w - MARGIN);
|
|
53
72
|
const maxY = Math.max(MARGIN, window.innerHeight - h - MARGIN);
|
|
@@ -146,7 +165,24 @@ export function ChatPopover({ alias, onClose }: ChatPopoverProps) {
|
|
|
146
165
|
<AliasAvatar alias={alias} size={28} />
|
|
147
166
|
<div className="min-w-0">
|
|
148
167
|
<div className="text-sm font-semibold text-[var(--fg)] truncate">{alias}</div>
|
|
149
|
-
|
|
168
|
+
{/* Round 37: cwd / last-seen lines surface the same metadata
|
|
169
|
+
the SVG <title> tooltip carries (rounds 33-34), but inside
|
|
170
|
+
the popover where it stays accessible after dragging the
|
|
171
|
+
window away from the node. Fall back to the drag-hint
|
|
172
|
+
when no metadata is reported. */}
|
|
173
|
+
{session?.project_dir ? (
|
|
174
|
+
<div className="text-[10px] text-[var(--fg-muted)] truncate font-mono" title={session.project_dir} data-popover-cwd>
|
|
175
|
+
cwd: {session.project_dir}
|
|
176
|
+
</div>
|
|
177
|
+
) : null}
|
|
178
|
+
{lastSeenLine ? (
|
|
179
|
+
<div className="text-[10px] text-[var(--fg-muted)] truncate" data-popover-lastseen>
|
|
180
|
+
last seen: {lastSeenLine}
|
|
181
|
+
</div>
|
|
182
|
+
) : null}
|
|
183
|
+
{!session?.project_dir && !lastSeenLine ? (
|
|
184
|
+
<div className="text-[10px] text-[var(--fg-muted)]">Drag to move · Esc to close</div>
|
|
185
|
+
) : null}
|
|
150
186
|
</div>
|
|
151
187
|
</div>
|
|
152
188
|
<button
|
|
@@ -44,6 +44,24 @@ const SHORTCUTS: { group: string; items: Shortcut[] }[] = [
|
|
|
44
44
|
{ keys: ['type'], label: 'Filter — searches commands, agents, tasks' },
|
|
45
45
|
],
|
|
46
46
|
},
|
|
47
|
+
// Round 25 / Loop: TopoGraph keys + mouse gestures from rounds 21-23
|
|
48
|
+
// were only surfaced in button titles. Consolidating here gives the
|
|
49
|
+
// Help overlay (the canonical discovery surface) a single place to
|
|
50
|
+
// learn the canvas controls.
|
|
51
|
+
{
|
|
52
|
+
group: 'Topology canvas',
|
|
53
|
+
items: [
|
|
54
|
+
{ keys: ['+'], label: 'Zoom in (also =)' },
|
|
55
|
+
{ keys: ['−'], label: 'Zoom out' },
|
|
56
|
+
{ keys: ['0'], label: 'Reset zoom + pan' },
|
|
57
|
+
{ keys: ['f'], label: 'Fit topology to canvas' },
|
|
58
|
+
{ keys: ['l'], label: 'Toggle layout (ring ↔ grid)' },
|
|
59
|
+
{ keys: ['⌘', 'wheel'], label: 'Zoom toward cursor (Ctrl on Windows/Linux)' },
|
|
60
|
+
{ keys: ['drag'], label: 'Pan the canvas (cursor → grabbing)' },
|
|
61
|
+
{ keys: ['dbl-click'], label: 'Reset zoom + pan' },
|
|
62
|
+
{ keys: ['click'], label: 'Open chat popover on a node (Esc to close)' },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
47
65
|
];
|
|
48
66
|
|
|
49
67
|
const GO_TARGETS: Record<string, string> = {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import useSWR from 'swr';
|
|
5
|
+
|
|
6
|
+
/** Issue #119 — server-side panel listing the hub's hosts + CPU/RAM bars.
|
|
7
|
+
*
|
|
8
|
+
* Steps 1+2 (SDK马 host telemetry + CommHub aggregation) shipped with
|
|
9
|
+
* commhub-server v0.8.1-preview.2. Step 3 (this file, Round 20 of the
|
|
10
|
+
* dashboard /loop) swaps the original mock seed for a SWR fetch of
|
|
11
|
+
* `/api/hub/servers` (which proxies to the hub's `/api/servers`).
|
|
12
|
+
*
|
|
13
|
+
* Collapsed by default to a thin vertical icon strip so it never competes
|
|
14
|
+
* with the topology graph for horizontal real-estate. State persists to
|
|
15
|
+
* localStorage just like the other dashboard drawers.
|
|
16
|
+
*
|
|
17
|
+
* Backward-compat: if the upstream hub predates v0.8.1-preview.2 the
|
|
18
|
+
* proxy returns `{ servers: [], unavailable: true }`. The drawer
|
|
19
|
+
* surfaces a friendly "not yet available" hint instead of an empty list,
|
|
20
|
+
* so old hubs don't read as "something broke".
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface ServerCard {
|
|
24
|
+
hostname: string;
|
|
25
|
+
ip?: string | null;
|
|
26
|
+
cpu_load_1min: number | null;
|
|
27
|
+
cpu_cores: number;
|
|
28
|
+
mem_used_gb: number | null;
|
|
29
|
+
mem_total_gb: number | null;
|
|
30
|
+
agent_count: number;
|
|
31
|
+
status: 'online' | 'offline';
|
|
32
|
+
note?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ServersResponse {
|
|
36
|
+
servers: ServerCard[];
|
|
37
|
+
unavailable?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const fetcher = async (url: string): Promise<ServersResponse> => {
|
|
41
|
+
const res = await fetch(url);
|
|
42
|
+
if (res.status === 401) {
|
|
43
|
+
if (typeof window !== 'undefined') window.location.assign('/login');
|
|
44
|
+
throw new Error('unauthorized');
|
|
45
|
+
}
|
|
46
|
+
if (!res.ok) throw new Error(`hub ${res.status}`);
|
|
47
|
+
return res.json();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Bar tint by usage — green ≤60%, amber 60–85%, red >85%. Keeps the eye
|
|
51
|
+
// going to whichever box is actually about to tip over.
|
|
52
|
+
function barTint(pct: number) {
|
|
53
|
+
if (pct >= 85) return { fill: '#ef4444', track: 'rgb(239 68 68 / 0.16)' };
|
|
54
|
+
if (pct >= 60) return { fill: '#f59e0b', track: 'rgb(245 158 11 / 0.16)' };
|
|
55
|
+
return { fill: '#10b981', track: 'rgb(16 185 129 / 0.16)' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function Bar({ pct, label }: { pct: number; label: string }) {
|
|
59
|
+
const t = barTint(pct);
|
|
60
|
+
const clamped = Math.max(0, Math.min(100, pct));
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-0.5">
|
|
63
|
+
<div className="flex justify-between text-[10px] text-[var(--fg-muted)] font-mono">
|
|
64
|
+
<span>{label}</span>
|
|
65
|
+
<span className="tabular-nums">{Math.round(clamped)}%</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div className="h-1.5 rounded-full overflow-hidden" style={{ background: t.track }}>
|
|
68
|
+
<div
|
|
69
|
+
className="h-full transition-[width] duration-300 ease-out"
|
|
70
|
+
style={{ width: `${clamped}%`, background: t.fill }}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function ServersDrawer() {
|
|
78
|
+
const [open, setOpen] = useState(false);
|
|
79
|
+
// Drawer state is per-user-machine — persist like the other dashboard
|
|
80
|
+
// sticky toggles (`anet-topo-layout`, `anet-topo-view`, etc.).
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
try { if (localStorage.getItem('anet-servers-drawer') === '1') setOpen(true); } catch {}
|
|
83
|
+
}, []);
|
|
84
|
+
const toggle = () => setOpen(prev => {
|
|
85
|
+
const next = !prev;
|
|
86
|
+
try { localStorage.setItem('anet-servers-drawer', next ? '1' : '0'); } catch {}
|
|
87
|
+
return next;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Round 20 / #119 step 3 final delivery — real SWR fetch. 5s refresh
|
|
91
|
+
// matches the other live drawers (HealthBanner, Sidebar) so an operator
|
|
92
|
+
// sees host telemetry update in roughly the same beat as session state.
|
|
93
|
+
// Only poll while expanded — collapsed icon strip doesn't need fresh data.
|
|
94
|
+
const { data, error } = useSWR<ServersResponse>(
|
|
95
|
+
open ? '/api/hub/servers' : null,
|
|
96
|
+
fetcher,
|
|
97
|
+
{ refreshInterval: 5000, dedupingInterval: 3000 },
|
|
98
|
+
);
|
|
99
|
+
const servers = data?.servers ?? [];
|
|
100
|
+
const unavailable = data?.unavailable === true;
|
|
101
|
+
const onlineCount = servers.filter(s => s.status === 'online').length;
|
|
102
|
+
const loading = open && !data && !error;
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<aside
|
|
106
|
+
className="fixed right-0 top-20 z-40 hidden lg:flex flex-col rounded-l-xl border border-r-0 shadow-xl shadow-black/30 anet-fade-in"
|
|
107
|
+
style={{
|
|
108
|
+
background: 'var(--bg-secondary)',
|
|
109
|
+
borderColor: 'var(--border)',
|
|
110
|
+
width: open ? 296 : 40,
|
|
111
|
+
maxHeight: 'calc(100vh - 6rem)',
|
|
112
|
+
transition: 'width 200ms ease-out',
|
|
113
|
+
}}
|
|
114
|
+
aria-label="Servers panel"
|
|
115
|
+
>
|
|
116
|
+
<button
|
|
117
|
+
onClick={toggle}
|
|
118
|
+
aria-expanded={open}
|
|
119
|
+
aria-label={open ? 'Collapse servers panel' : 'Expand servers panel'}
|
|
120
|
+
className="flex items-center gap-2 px-2.5 py-2 border-b text-[11px] font-semibold tracking-wide select-none"
|
|
121
|
+
style={{ color: 'var(--fg)', borderColor: 'var(--border)' }}
|
|
122
|
+
>
|
|
123
|
+
<svg className="w-4 h-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
|
124
|
+
<rect x="3" y="4" width="18" height="6" rx="1.5" />
|
|
125
|
+
<rect x="3" y="14" width="18" height="6" rx="1.5" />
|
|
126
|
+
<circle cx="7" cy="7" r="0.7" fill="currentColor" />
|
|
127
|
+
<circle cx="7" cy="17" r="0.7" fill="currentColor" />
|
|
128
|
+
</svg>
|
|
129
|
+
{open && (
|
|
130
|
+
<>
|
|
131
|
+
<span className="flex-1 text-left">Servers</span>
|
|
132
|
+
<span className="text-[10px] text-[var(--fg-muted)] font-mono">{onlineCount}/{servers.length}</span>
|
|
133
|
+
</>
|
|
134
|
+
)}
|
|
135
|
+
</button>
|
|
136
|
+
|
|
137
|
+
{open && (
|
|
138
|
+
<div className="flex-1 min-h-0 overflow-y-auto px-2 py-2 space-y-2" data-servers-body>
|
|
139
|
+
{loading && (
|
|
140
|
+
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-4">
|
|
141
|
+
loading servers…
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
{error && !data && (
|
|
145
|
+
<div className="rounded-md border px-2.5 py-2 text-[10px] font-mono"
|
|
146
|
+
style={{ background: 'rgb(239 68 68 / 0.06)', borderColor: 'rgb(239 68 68 / 0.25)', color: '#ef4444' }}>
|
|
147
|
+
hub unreachable · retrying every 5s
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
{unavailable && (
|
|
151
|
+
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-3 leading-relaxed">
|
|
152
|
+
host telemetry not available<br/>
|
|
153
|
+
<span className="text-[var(--fg-dim)]">upgrade commhub-server ≥ 0.8.1-preview.2</span>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
{!loading && !error && !unavailable && servers.length === 0 && (
|
|
157
|
+
<div className="text-[10px] text-[var(--fg-muted)] font-mono text-center py-4">
|
|
158
|
+
no servers reporting yet
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
{servers.map(s => {
|
|
162
|
+
const offline = s.status === 'offline';
|
|
163
|
+
const cpuPct = s.cpu_load_1min != null && s.cpu_cores > 0 ? (s.cpu_load_1min / s.cpu_cores) * 100 : null;
|
|
164
|
+
const memPct = s.mem_used_gb != null && s.mem_total_gb != null && s.mem_total_gb > 0 ? (s.mem_used_gb / s.mem_total_gb) * 100 : null;
|
|
165
|
+
return (
|
|
166
|
+
<div
|
|
167
|
+
key={s.hostname}
|
|
168
|
+
className="rounded-lg border px-2.5 py-2 space-y-1.5"
|
|
169
|
+
style={{
|
|
170
|
+
background: 'var(--bg)',
|
|
171
|
+
borderColor: 'var(--border)',
|
|
172
|
+
opacity: offline ? 0.55 : 1,
|
|
173
|
+
}}
|
|
174
|
+
title={s.ip ? `${s.hostname} · ${s.ip}` : s.hostname}
|
|
175
|
+
>
|
|
176
|
+
<div className="flex items-baseline justify-between gap-2">
|
|
177
|
+
<div className="min-w-0 flex items-baseline gap-1.5">
|
|
178
|
+
<span className="font-mono text-[12px] font-semibold truncate" style={{ color: 'var(--fg)' }}>{s.hostname}</span>
|
|
179
|
+
{s.note && <span className="text-[9px] text-[var(--fg-dim)]">({s.note})</span>}
|
|
180
|
+
</div>
|
|
181
|
+
<span className="text-[10px] text-[var(--fg-muted)] font-mono tabular-nums shrink-0">
|
|
182
|
+
{s.agent_count} agent{s.agent_count === 1 ? '' : 's'}
|
|
183
|
+
</span>
|
|
184
|
+
</div>
|
|
185
|
+
{!offline && cpuPct != null && (
|
|
186
|
+
<Bar pct={cpuPct} label={`CPU ${s.cpu_load_1min!.toFixed(2)}/${s.cpu_cores}`} />
|
|
187
|
+
)}
|
|
188
|
+
{!offline && memPct != null && (
|
|
189
|
+
<Bar pct={memPct} label={`RAM ${s.mem_used_gb!.toFixed(1)}/${s.mem_total_gb!.toFixed(1)}G`} />
|
|
190
|
+
)}
|
|
191
|
+
{offline && (
|
|
192
|
+
<div className="text-[10px] text-[var(--fg-dim)] font-mono italic">CPU n/a · RAM n/a · offline</div>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
})}
|
|
197
|
+
{servers.length > 0 && (
|
|
198
|
+
<div className="pt-1 text-[9px] text-[var(--fg-dim)] font-mono text-center">
|
|
199
|
+
live · refreshing every 5s
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
204
|
+
</aside>
|
|
205
|
+
);
|
|
206
|
+
}
|