@sleep2agi/agent-network-dashboard 0.4.6-preview.4 → 0.4.6-preview.41
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/chunks/{node_modules_13sb.px._.js → 00jm_02mlcew._.js} +167 -167
- package/.next/build/chunks/00jm_02mlcew._.js.map +47 -0
- package/.next/build/chunks/{[root-of-the-server]__0ubbtyl._.js → [root-of-the-server]__0tgc-8p._.js} +3 -3
- package/.next/build/chunks/{[root-of-the-server]__0d-m0h0._.js → [root-of-the-server]__13z64y.._.js} +7 -7
- package/.next/build/chunks/{[root-of-the-server]__0d-m0h0._.js.map → [root-of-the-server]__13z64y.._.js.map} +2 -2
- package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_0r4x5ys._.js +13 -0
- package/.next/build/chunks/[turbopack]_runtime.js +1 -1
- package/.next/build/postcss.js +4 -4
- package/.next/build-manifest.json +8 -8
- package/.next/diagnostics/route-bundle-stats.json +157 -143
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +4 -4
- package/.next/required-server-files.json +4 -4
- package/.next/routes-manifest.json +6 -0
- package/.next/server/app/_global-error/page/build-manifest.json +5 -5
- package/.next/server/app/_global-error/page.js +9 -9
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +8 -8
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_global-error.segments/_full.segment.rsc +8 -8
- package/.next/server/app/_global-error.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page/build-manifest.json +5 -5
- package/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
- package/.next/server/app/_not-found/page.js +12 -12
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page/build-manifest.json +5 -5
- package/.next/server/app/admin/page/next-font-manifest.json +1 -1
- package/.next/server/app/admin/page.js +13 -13
- package/.next/server/app/admin/page.js.nft.json +1 -1
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/api/anet/config/route.js +6 -6
- package/.next/server/app/api/anet/config/route.js.nft.json +1 -1
- package/.next/server/app/api/auth/login/route.js +7 -7
- package/.next/server/app/api/auth/login/route.js.nft.json +1 -1
- package/.next/server/app/api/auth/logout/route.js +7 -7
- package/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/server/app/api/auth/v3/route.js +7 -7
- package/.next/server/app/api/auth/v3/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/audit-log/route.js +6 -6
- package/.next/server/app/api/hub/audit-log/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/auth/route.js +7 -7
- package/.next/server/app/api/hub/auth/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/broadcast/route.js +6 -6
- package/.next/server/app/api/hub/broadcast/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/events/route.js +6 -6
- package/.next/server/app/api/hub/events/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/health/route.js +6 -6
- package/.next/server/app/api/hub/health/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/inbox/route.js +6 -6
- package/.next/server/app/api/hub/inbox/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/license/route.js +6 -6
- package/.next/server/app/api/hub/license/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/messages/route.js +6 -6
- package/.next/server/app/api/hub/messages/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/networks/route.js +6 -6
- package/.next/server/app/api/hub/networks/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/nodes/route.js +6 -6
- package/.next/server/app/api/hub/nodes/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/register/route.js +6 -6
- package/.next/server/app/api/hub/register/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/send/route.js +6 -6
- package/.next/server/app/api/hub/send/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/server-logs/route.js +6 -6
- package/.next/server/app/api/hub/server-logs/route.js.nft.json +1 -1
- 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/api/hub/session/route.js +6 -6
- package/.next/server/app/api/hub/session/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/stats/route.js +6 -6
- package/.next/server/app/api/hub/stats/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/status/route.js +6 -6
- package/.next/server/app/api/hub/status/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/task-events/route.js +6 -6
- package/.next/server/app/api/hub/task-events/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/tasks/route.js +6 -6
- package/.next/server/app/api/hub/tasks/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/tmux/route.js +6 -6
- package/.next/server/app/api/hub/tmux/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/tokens/route.js +6 -6
- package/.next/server/app/api/hub/tokens/route.js.nft.json +1 -1
- package/.next/server/app/favicon.ico/route.js +6 -6
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +14 -14
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page/build-manifest.json +5 -5
- package/.next/server/app/login/page/next-font-manifest.json +1 -1
- package/.next/server/app/login/page.js +13 -13
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +14 -14
- package/.next/server/app/login.segments/_full.segment.rsc +14 -14
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- package/.next/server/app/logs/page/build-manifest.json +5 -5
- package/.next/server/app/logs/page/next-font-manifest.json +1 -1
- package/.next/server/app/logs/page.js +13 -13
- package/.next/server/app/logs/page.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +14 -14
- package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- package/.next/server/app/messages/page/build-manifest.json +5 -5
- package/.next/server/app/messages/page/next-font-manifest.json +1 -1
- package/.next/server/app/messages/page.js +13 -13
- package/.next/server/app/messages/page.js.nft.json +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +14 -14
- package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- package/.next/server/app/node/page/build-manifest.json +5 -5
- package/.next/server/app/node/page/next-font-manifest.json +1 -1
- package/.next/server/app/node/page.js +13 -13
- package/.next/server/app/node/page.js.nft.json +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +14 -14
- package/.next/server/app/node.segments/_full.segment.rsc +14 -14
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- package/.next/server/app/nodes/page/build-manifest.json +5 -5
- package/.next/server/app/nodes/page/next-font-manifest.json +1 -1
- package/.next/server/app/nodes/page.js +13 -13
- package/.next/server/app/nodes/page.js.nft.json +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +14 -14
- package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- package/.next/server/app/page/build-manifest.json +5 -5
- package/.next/server/app/page/next-font-manifest.json +1 -1
- package/.next/server/app/page.js +13 -13
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page/build-manifest.json +5 -5
- package/.next/server/app/server-logs/page/next-font-manifest.json +1 -1
- package/.next/server/app/server-logs/page.js +13 -13
- package/.next/server/app/server-logs/page.js.nft.json +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +14 -14
- package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- package/.next/server/app/settings/networks/page/build-manifest.json +5 -5
- package/.next/server/app/settings/networks/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/networks/page.js +13 -13
- package/.next/server/app/settings/networks/page.js.nft.json +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings/page/build-manifest.json +5 -5
- package/.next/server/app/settings/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/page.js +13 -13
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page/build-manifest.json +5 -5
- package/.next/server/app/settings/tokens/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/tokens/page.js +13 -13
- package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +14 -14
- package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/app/tasks/[id]/page/build-manifest.json +5 -5
- package/.next/server/app/tasks/[id]/page/next-font-manifest.json +1 -1
- package/.next/server/app/tasks/[id]/page.js +13 -13
- package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page/build-manifest.json +5 -5
- package/.next/server/app/tasks/page/next-font-manifest.json +1 -1
- package/.next/server/app/tasks/page.js +13 -13
- package/.next/server/app/tasks/page.js.nft.json +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/app-paths-manifest.json +1 -0
- package/.next/server/chunks/00jm_next_04f3gac._.js +10 -0
- package/.next/server/chunks/00jm_next_04f3gac._.js.map +1 -0
- package/.next/server/chunks/00jm_next_dist_esm_build_templates_app-route_02f430o.js +3 -0
- package/.next/server/chunks/00jm_next_dist_esm_build_templates_app-route_02f430o.js.map +1 -0
- package/.next/server/chunks/0ci9_nt-network-dashboard__next-internal_server_app_api_auth_v3_route_actions_0f5nbzr.js +3 -0
- package/.next/server/chunks/0ci9_nt-network-dashboard__next-internal_server_app_favicon_ico_route_actions_0t0eu9r.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_anet_config_route_actions_0a~v55_.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_auth_login_route_actions_0xnxfs4.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_auth_logout_route_actions_0p8p0rn.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_audit-log_route_actions_06uquwi.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_auth_route_actions_0f_y1ey.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_broadcast_route_actions_0ao3j43.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_events_route_actions_0_5ndld.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_health_route_actions_0mpzl74.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_inbox_route_actions_0stoyto.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_license_route_actions_0a4.fuh.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_messages_route_actions_059rw1o.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_networks_route_actions_03sehpn.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_nodes_route_actions_11fbp28.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_register_route_actions_0l.d7~n.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_send_route_actions_0.p82vy.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_server-logs_route_actions_05~c5lb.js +3 -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_session_route_actions_0b9ef2i.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_stats_route_actions_03u65u0.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_status_route_actions_0_71ear.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_task-events_route_actions_07md_ee.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_tasks_route_actions_0er3uvk.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_tmux_route_actions_0o_1-8u.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_tokens_route_actions_09doluz.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0-2le8-._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0-2le8-._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0-g~15e._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0-g~15e._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__00194.t._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__03ueci3._.js.map → [root-of-the-server]__00194.t._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__02p2n3n._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__02p2n3n._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__02y-i-p._.js +6 -0
- package/.next/server/chunks/[root-of-the-server]__02y-i-p._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__043tnx2._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0o4qfn9._.js.map → [root-of-the-server]__043tnx2._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__04z62zk._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__04z62zk._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__06p6kkp._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__06p6kkp._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__07ztet2._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__07ztet2._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__08uyvdq._.js +20 -0
- package/.next/server/chunks/[root-of-the-server]__08uyvdq._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0b7v51y._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0b7v51y._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0e3ucl1._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0rg9ifx._.js.map → [root-of-the-server]__0e3ucl1._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0e6yail._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0e6yail._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0gx2_6a._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0gx2_6a._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0hcnyp.._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0o29v8c._.js.map → [root-of-the-server]__0hcnyp.._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0hg3_8r._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0hg3_8r._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0mxw4vb._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0mxw4vb._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0mzopco._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0mzopco._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0o45a4q._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0p6tutt._.js.map → [root-of-the-server]__0o45a4q._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0q-teo6._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0q-teo6._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0qcxxzf._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0qcxxzf._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0rovr5-._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0rovr5-._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0~h2uqi._.js.map → [root-of-the-server]__0tx8s8i._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0xtp-et._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0xtp-et._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__10pvabe._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__10pvabe._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__10t11zq._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__10t11zq._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__12r~omy._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__12r~omy._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__1389~cs._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1389~cs._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__13n8t..._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__13n8t..._.js.map +1 -0
- package/.next/server/chunks/[turbopack]_runtime.js +1 -1
- package/.next/server/chunks/ssr/{node_modules_0r_.s40._.js → 00jm_03rn9vn._.js} +2 -2
- package/.next/server/chunks/ssr/00jm_03rn9vn._.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_09w7yel._.js → 00jm_059ns68._.js} +3 -3
- package/.next/server/chunks/ssr/00jm_059ns68._.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_09ae-q0._.js +3 -0
- package/.next/server/chunks/ssr/00jm_09ae-q0._.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_0j7__z.._.js +3 -0
- package/.next/server/chunks/ssr/00jm_0j7__z.._.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_next_01-0urn._.js +6 -0
- package/.next/server/chunks/ssr/00jm_next_01-0urn._.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_next_dist_05rlh5d._.js +6 -0
- package/.next/server/chunks/ssr/00jm_next_dist_05rlh5d._.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_next_dist_client_components_0gkudcq._.js +3 -0
- package/.next/server/chunks/ssr/00jm_next_dist_client_components_0gkudcq._.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_forbidden_0ecus3e.js +3 -0
- package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_forbidden_0ecus3e.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_global-error_07n8pg2.js +3 -0
- package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_global-error_07n8pg2.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_unauthorized_0r0uq4j.js +3 -0
- package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_unauthorized_0r0uq4j.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_002l7yi.js → 00jm_next_dist_esm_build_templates_app-page_0..ddwm.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0..ddwm.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0qm1-a1.js → 00jm_next_dist_esm_build_templates_app-page_02bxi6j.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_02bxi6j.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_02suzhc.js → 00jm_next_dist_esm_build_templates_app-page_05oassd.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05oassd.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0dm~kj8.js → 00jm_next_dist_esm_build_templates_app-page_05vxtby.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05vxtby.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0uccf9z.js → 00jm_next_dist_esm_build_templates_app-page_05~eml4.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05~eml4.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0l-fh0i.js → 00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0uv6jyc.js → 00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js +4 -0
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_13s07li.js → 00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0o9vt1n.js → 00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0nsg22r.js → 00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0d2yq5t.js → 00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0foejvu.js → 00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js.map +1 -0
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js +4 -0
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js.map +1 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_07uu-xq.js → 00jm_next_dist_esm_build_templates_app-page_13mqkay.js} +3 -3
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_13mqkay.js.map +1 -0
- package/.next/server/chunks/ssr/0dwh_ent-network-dashboard__next-internal_server_app_server-logs_page_actions_0hkk815.js +3 -0
- package/.next/server/chunks/ssr/0yas_gent-network-dashboard__next-internal_server_app__not-found_page_actions_0u.f-rz.js +3 -0
- package/.next/server/chunks/ssr/0yas_gent-network-dashboard__next-internal_server_app_tasks_[id]_page_actions_0r3~lho.js +3 -0
- package/.next/server/chunks/ssr/0ykm__next-internal_server_app__global-error_page_actions_100fxls.js +3 -0
- package/.next/server/chunks/ssr/0ykm__next-internal_server_app_settings_networks_page_actions_0jgy57m.js +3 -0
- package/.next/server/chunks/ssr/0ykm__next-internal_server_app_settings_tokens_page_actions_0l5h.k~.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__063~dul._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__063~dul._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__07io.d8._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__07io.d8._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__08d6lp4._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__08d6lp4._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__09f1wn6._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__09f1wn6._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0c4xtkg._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0c4xtkg._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0erfzjn._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0erfzjn._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0ezjwje._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0ezjwje._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__08qr2ji._.js → [root-of-the-server]__0gc.4uk._.js} +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0gc.4uk._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0h_p~-4._.js +19 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0h_p~-4._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0jdr1e5._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0jdr1e5._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0lw16mu._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0lw16mu._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0mjly~-._.js +33 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0mjly~-._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0p-95r_._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0p-95r_._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js +8 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0ue5.fy._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0ue5.fy._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0uggxvx._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0uggxvx._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0zjk5ro._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0zjk5ro._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0zz3v7a._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0zz3v7a._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0~y12ef._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0~y12ef._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__129sc_9._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__129sc_9._.js.map +1 -0
- package/.next/server/chunks/ssr/[turbopack]_runtime.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_admin_page_actions_0i~.x~j.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_login_page_actions_0frwl15.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_logs_page_actions_0xa2ox_.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_messages_page_actions_10j3tg4.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_node_page_actions_0sl4ntd.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_nodes_page_actions_0cpgkfs.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_page_actions_08nuo36.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_settings_page_actions_0iv.lmk.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_tasks_page_actions_08c7qlx.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_tasks_page_actions_08c7qlx.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -0
- package/.next/server/chunks/ssr/{app_0izi28j._.js → agent-network-dashboard_app_0xgney8._.js} +2 -2
- package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -0
- package/.next/server/chunks/ssr/{app_0_agk0r._.js → agent-network-dashboard_app_10hjgv4._.js} +2 -2
- package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -0
- package/.next/server/chunks/ssr/{app_0.zbtn5._.js → agent-network-dashboard_app_1153xeb._.js} +2 -2
- package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -0
- package/.next/server/chunks/ssr/{app_04n78pr._.js → agent-network-dashboard_app_12l4oto._.js} +2 -2
- package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -0
- package/.next/server/chunks/ssr/{app_components_08ys5y3._.js → agent-network-dashboard_app_components_0mvyi-4._.js} +3 -3
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -0
- package/.next/server/chunks/ssr/{app_server-logs_page_tsx_0_kpp1p._.js → agent-network-dashboard_app_server-logs_page_tsx_0dg.l_8._.js} +2 -2
- package/.next/server/chunks/ssr/agent-network-dashboard_app_server-logs_page_tsx_0dg.l_8._.js.map +1 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js +3 -0
- package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js.map +1 -0
- package/.next/server/middleware-build-manifest.js +8 -8
- package/.next/server/middleware.js +3 -3
- package/.next/server/middleware.js.nft.json +1 -1
- package/.next/server/next-font-manifest.js +1 -1
- package/.next/server/next-font-manifest.json +14 -14
- 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-7iowzu4.798.js +1 -0
- package/.next/static/chunks/{0gtbwcilfu6b7.js → 0.66f3.rtcybb.js} +1 -1
- package/.next/static/chunks/02t5jr4shh.hy.js +1 -0
- package/.next/static/chunks/039-b.v_9-_rm.js +1 -0
- package/.next/static/chunks/05oi25hh7b81r.js +31 -0
- package/.next/static/chunks/07~8sx3_5uiox.js +1 -0
- package/.next/static/chunks/{0pqt~8bl3ukh4.js → 0_pxg.4pebw__.js} +2 -2
- package/.next/static/chunks/0_qfg5s5uzs7..js +7 -0
- package/.next/static/chunks/0b8wnt4put-5n.js +2 -0
- package/.next/static/chunks/0elalb9n_.~di.js +1 -0
- package/.next/static/chunks/{01xlw8hd842-c.js → 0eov~nkwaf-qa.js} +1 -1
- package/.next/static/chunks/0j_q__o8p6g83.js +1 -0
- package/.next/static/chunks/0jl6_w50zh4l5.js +1 -0
- package/.next/static/chunks/{0w1zwt8kq_7nb.js → 0ku0fjqlm9mca.js} +1 -1
- package/.next/static/chunks/0mqwn-l7wtcdo.js +7 -0
- package/.next/static/chunks/0r4q73.-1bkyl.js +1 -0
- package/.next/static/chunks/0r_c8eccg3z2o.js +1 -0
- package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
- package/.next/static/chunks/0tz5gaflhax.o.js +1 -0
- package/.next/static/chunks/0u3b93reo9xci.js +1 -0
- package/.next/static/chunks/10kk9kuoj0ns9.js +1 -0
- package/.next/static/chunks/10oi7l-tycgl9.js +1 -0
- package/.next/static/chunks/11l042tnn68ye.js +1 -0
- package/.next/static/chunks/11uk1izuv3~0t.js +1 -0
- package/.next/static/chunks/13.zti9ix.1eh.css +2 -0
- package/.next/static/chunks/{turbopack-01f1xs~cq_~si.js → turbopack-0.7dvsf9rllt0.js} +1 -1
- 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 +6 -6
- package/app/components/HelpOverlay.tsx +18 -0
- package/app/components/ServersDrawer.tsx +206 -0
- package/app/components/TaskChatPanel.tsx +39 -39
- package/app/components/TopoGraph.tsx +909 -148
- package/app/components/types.ts +3 -0
- package/app/page.tsx +18 -1
- package/package.json +1 -1
- package/scripts/chat-popover-test.mjs +2 -0
- package/scripts/chat-theme-test.mjs +98 -0
- package/scripts/help-overlay-topo-test.mjs +44 -0
- package/scripts/servers-drawer-live-test.mjs +96 -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-fit-key-test.mjs +106 -0
- package/scripts/topo-group-count-test.mjs +63 -0
- package/scripts/topo-group-hover-test.mjs +82 -0
- package/scripts/topo-groupbox-test.mjs +118 -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-layout-test.mjs +1 -0
- package/scripts/topo-minimap-test.mjs +68 -0
- package/scripts/topo-offline-dim-test.mjs +70 -0
- package/scripts/topo-overlap-test.mjs +164 -0
- package/scripts/topo-p0round-test.mjs +166 -0
- package/scripts/topo-pan-cursor-test.mjs +62 -0
- package/scripts/topo-pressure-bar-test.mjs +69 -0
- package/scripts/topo-pulse-rate-test.mjs +71 -0
- package/scripts/topo-rename-test.mjs +107 -0
- package/scripts/topo-vendor-test.mjs +1 -0
- package/scripts/topo-wheel-zoom-test.mjs +79 -0
- package/scripts/topo-zoom-keys-test.mjs +89 -0
- package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_06e.r3r._.js +0 -13
- package/.next/build/chunks/node_modules_13sb.px._.js.map +0 -47
- package/.next/server/chunks/[root-of-the-server]__0-mykaf._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0-mykaf._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0.d_kqy._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0.d_kqy._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__02pocyg._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__02pocyg._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__03ueci3._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__04aud7x._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__04aud7x._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__05fnt3n._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__05fnt3n._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__077tsd.._.js +0 -20
- package/.next/server/chunks/[root-of-the-server]__077tsd.._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__08wy1jr._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__08wy1jr._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0adhu1m._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0adhu1m._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0aqv02v._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0aqv02v._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0cawhfq._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0cawhfq._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0d_-_k.._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0d_-_k.._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0f6bxfl._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0f6bxfl._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0h15edq._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0h15edq._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0k.vegl._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0k.vegl._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0kltbce._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0kltbce._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0mn04fw._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0mn04fw._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0o29v8c._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0o4qfn9._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0o7ya7r._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0o7ya7r._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0p3fo6l._.js +0 -6
- package/.next/server/chunks/[root-of-the-server]__0p3fo6l._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0p6tutt._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0pn3wsx._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0pn3wsx._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0rg9ifx._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0x.-194._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0x.-194._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0x.a_yh._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0x.a_yh._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0~h2uqi._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__139~d6a._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__139~d6a._.js.map +0 -1
- package/.next/server/chunks/_next-internal_server_app_api_anet_config_route_actions_0xzy73j.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_auth_login_route_actions_0zukc38.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_auth_logout_route_actions_0fm~3ij.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_auth_v3_route_actions_0ndzs5l.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_audit-log_route_actions_11k8fqt.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_auth_route_actions_0gs7ttk.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_broadcast_route_actions_0ads-kd.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_events_route_actions_0opktby.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_health_route_actions_0tzzt~x.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_inbox_route_actions_0xvslrg.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_license_route_actions_0i3tlr3.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_messages_route_actions_08-gi-s.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_networks_route_actions_02vejq2.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_nodes_route_actions_0nx8ryn.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_register_route_actions_086wi_3.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_send_route_actions_0-ga_ug.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_server-logs_route_actions_00euexv.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_session_route_actions_041wa62.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_stats_route_actions_1242~od.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_status_route_actions_0aml09a.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_task-events_route_actions_02v1tmm.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_tasks_route_actions_0nl77v4.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_tmux_route_actions_0u~aaza.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_hub_tokens_route_actions_09hzf_1.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_095lj93.js +0 -3
- package/.next/server/chunks/node_modules_next_11synfn._.js +0 -10
- package/.next/server/chunks/node_modules_next_11synfn._.js.map +0 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0yubv2d.js +0 -3
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0yubv2d.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__014uja_._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__014uja_._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__02hi65f._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__02hi65f._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__02xqa5z._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__02xqa5z._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__04ph1de._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__04ph1de._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__05wc5-z._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__05wc5-z._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__06-2p1a._.js +0 -33
- package/.next/server/chunks/ssr/[root-of-the-server]__06-2p1a._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__08qr2ji._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__09l8xaa._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__09l8xaa._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__09z7o2x._.js +0 -19
- package/.next/server/chunks/ssr/[root-of-the-server]__09z7o2x._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0a0ffmv._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0a0ffmv._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0b7uu-_._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0b7uu-_._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0bl_wqk._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0bl_wqk._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0ft0rpu._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0ft0rpu._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0i~g-2t._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0i~g-2t._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0mook08._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0mook08._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0qchqb~._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0qchqb~._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0rrm~-q._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0rrm~-q._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0xjish~._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0xjish~._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0ywihy_._.js +0 -8
- package/.next/server/chunks/ssr/[root-of-the-server]__0ywihy_._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__12ad1n.._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__12ad1n.._.js.map +0 -1
- package/.next/server/chunks/ssr/_0nne7mz._.js +0 -3
- package/.next/server/chunks/ssr/_0nne7mz._.js.map +0 -1
- package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_admin_page_actions_0ycnu8f.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_login_page_actions_02kefem.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_logs_page_actions_0rvsl~d.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_messages_page_actions_0gsqep_.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_node_page_actions_0wd8cgl.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_nodes_page_actions_0-.rv67.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_server-logs_page_actions_0g1gb5c.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_settings_networks_page_actions_09uw.sw.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_settings_page_actions_0mr68ai.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_settings_tokens_page_actions_0yi54x8.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_tasks_[id]_page_actions_0k_hm9g.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_tasks_page_actions_0_2o_cr.js +0 -3
- package/.next/server/chunks/ssr/app_0.zbtn5._.js.map +0 -1
- package/.next/server/chunks/ssr/app_01y-~mp._.js +0 -3
- package/.next/server/chunks/ssr/app_01y-~mp._.js.map +0 -1
- package/.next/server/chunks/ssr/app_04n78pr._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0_agk0r._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0aenxef._.js +0 -3
- package/.next/server/chunks/ssr/app_0aenxef._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0aum_g5._.js +0 -3
- package/.next/server/chunks/ssr/app_0aum_g5._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0emvkn2._.js +0 -3
- package/.next/server/chunks/ssr/app_0emvkn2._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0esuljp._.js +0 -3
- package/.next/server/chunks/ssr/app_0esuljp._.js.map +0 -1
- package/.next/server/chunks/ssr/app_0izi28j._.js.map +0 -1
- package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +0 -1
- package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js.map +0 -1
- package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +0 -3
- package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_09w7yel._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_0r_.s40._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_0vtdjw7._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_0vtdjw7._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js +0 -6
- package/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_0e1izl_._.js +0 -6
- package/.next/server/chunks/ssr/node_modules_next_dist_0e1izl_._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js +0 -3
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js +0 -3
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js +0 -3
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_002l7yi.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_02suzhc.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_07uu-xq.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0d2yq5t.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dm~kj8.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0foejvu.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0l-fh0i.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0nsg22r.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0o9vt1n.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0qm1-a1.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0uccf9z.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0uv6jyc.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10~~o-h.js +0 -4
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10~~o-h.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13leyhk.js +0 -4
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13leyhk.js.map +0 -1
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13s07li.js.map +0 -1
- package/.next/static/chunks/0-hlcuoemhe7w.js +0 -7
- package/.next/static/chunks/0257pdz1-imal.js +0 -2
- package/.next/static/chunks/02_zpo7taex9o.js +0 -7
- package/.next/static/chunks/04p678v0aui~f.js +0 -1
- package/.next/static/chunks/06n3d~fbbtagk.js +0 -1
- package/.next/static/chunks/07eba1lz2u3p4.js +0 -1
- package/.next/static/chunks/07epshqd2k_l_.js +0 -1
- package/.next/static/chunks/09n9xs.6dvvo2.css +0 -2
- package/.next/static/chunks/0av2~0.96~d~g.js +0 -1
- package/.next/static/chunks/0c1bp~g7.8ru..js +0 -1
- package/.next/static/chunks/0e-.zgw81gela.js +0 -31
- package/.next/static/chunks/0g9aof0ob_fcj.js +0 -1
- package/.next/static/chunks/0s8jo6jypzkx5.js +0 -1
- package/.next/static/chunks/0sxaea_g05hbz.js +0 -1
- package/.next/static/chunks/0u359llqm1_01.js +0 -1
- package/.next/static/chunks/0ubqqhhpsaez4.js +0 -1
- package/.next/static/chunks/0~qw9kxksr1v2.js +0 -1
- package/.next/static/chunks/12gprw3l0kvvf.js +0 -1
- package/.next/static/chunks/12yuo353giomx.js +0 -1
- package/.next/static/chunks/14bsg3_-taiw4.js +0 -1
- /package/.next/build/chunks/{[root-of-the-server]__0ubbtyl._.js.map → [root-of-the-server]__0tgc-8p._.js.map} +0 -0
- /package/.next/build/chunks/{[turbopack-node]_transforms_postcss_ts_06e.r3r._.js.map → [turbopack-node]_transforms_postcss_ts_0r4x5ys._.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_anet_config_route_actions_0xzy73j.js.map → 0ci9_nt-network-dashboard__next-internal_server_app_api_auth_v3_route_actions_0f5nbzr.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_auth_login_route_actions_0zukc38.js.map → 0ci9_nt-network-dashboard__next-internal_server_app_favicon_ico_route_actions_0t0eu9r.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_auth_logout_route_actions_0fm~3ij.js.map → 0ykm__next-internal_server_app_api_anet_config_route_actions_0a~v55_.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_auth_v3_route_actions_0ndzs5l.js.map → 0ykm__next-internal_server_app_api_auth_login_route_actions_0xnxfs4.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_audit-log_route_actions_11k8fqt.js.map → 0ykm__next-internal_server_app_api_auth_logout_route_actions_0p8p0rn.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_auth_route_actions_0gs7ttk.js.map → 0ykm__next-internal_server_app_api_hub_audit-log_route_actions_06uquwi.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_broadcast_route_actions_0ads-kd.js.map → 0ykm__next-internal_server_app_api_hub_auth_route_actions_0f_y1ey.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_events_route_actions_0opktby.js.map → 0ykm__next-internal_server_app_api_hub_broadcast_route_actions_0ao3j43.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_health_route_actions_0tzzt~x.js.map → 0ykm__next-internal_server_app_api_hub_events_route_actions_0_5ndld.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_inbox_route_actions_0xvslrg.js.map → 0ykm__next-internal_server_app_api_hub_health_route_actions_0mpzl74.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_license_route_actions_0i3tlr3.js.map → 0ykm__next-internal_server_app_api_hub_inbox_route_actions_0stoyto.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_messages_route_actions_08-gi-s.js.map → 0ykm__next-internal_server_app_api_hub_license_route_actions_0a4.fuh.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_networks_route_actions_02vejq2.js.map → 0ykm__next-internal_server_app_api_hub_messages_route_actions_059rw1o.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_nodes_route_actions_0nx8ryn.js.map → 0ykm__next-internal_server_app_api_hub_networks_route_actions_03sehpn.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_register_route_actions_086wi_3.js.map → 0ykm__next-internal_server_app_api_hub_nodes_route_actions_11fbp28.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_send_route_actions_0-ga_ug.js.map → 0ykm__next-internal_server_app_api_hub_register_route_actions_0l.d7~n.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_server-logs_route_actions_00euexv.js.map → 0ykm__next-internal_server_app_api_hub_send_route_actions_0.p82vy.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_session_route_actions_041wa62.js.map → 0ykm__next-internal_server_app_api_hub_server-logs_route_actions_05~c5lb.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_stats_route_actions_1242~od.js.map → 0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_status_route_actions_0aml09a.js.map → 0ykm__next-internal_server_app_api_hub_session_route_actions_0b9ef2i.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_task-events_route_actions_02v1tmm.js.map → 0ykm__next-internal_server_app_api_hub_stats_route_actions_03u65u0.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_tasks_route_actions_0nl77v4.js.map → 0ykm__next-internal_server_app_api_hub_status_route_actions_0_71ear.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_tmux_route_actions_0u~aaza.js.map → 0ykm__next-internal_server_app_api_hub_task-events_route_actions_07md_ee.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_api_hub_tokens_route_actions_09hzf_1.js.map → 0ykm__next-internal_server_app_api_hub_tasks_route_actions_0er3uvk.js.map} +0 -0
- /package/.next/server/chunks/{_next-internal_server_app_favicon_ico_route_actions_095lj93.js.map → 0ykm__next-internal_server_app_api_hub_tmux_route_actions_0o_1-8u.js.map} +0 -0
- /package/.next/server/chunks/{ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js.map → 0ykm__next-internal_server_app_api_hub_tokens_route_actions_09doluz.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app__not-found_page_actions_0eq97pa.js.map → 0dwh_ent-network-dashboard__next-internal_server_app_server-logs_page_actions_0hkk815.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_admin_page_actions_0ycnu8f.js.map → 0yas_gent-network-dashboard__next-internal_server_app__not-found_page_actions_0u.f-rz.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_login_page_actions_02kefem.js.map → 0yas_gent-network-dashboard__next-internal_server_app_tasks_[id]_page_actions_0r3~lho.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_logs_page_actions_0rvsl~d.js.map → 0ykm__next-internal_server_app__global-error_page_actions_100fxls.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_messages_page_actions_0gsqep_.js.map → 0ykm__next-internal_server_app_settings_networks_page_actions_0jgy57m.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_node_page_actions_0wd8cgl.js.map → 0ykm__next-internal_server_app_settings_tokens_page_actions_0l5h.k~.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_nodes_page_actions_0-.rv67.js.map → agent-network-dashboard__next-internal_server_app_admin_page_actions_0i~.x~j.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_page_actions_09-gtaw.js.map → agent-network-dashboard__next-internal_server_app_login_page_actions_0frwl15.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_server-logs_page_actions_0g1gb5c.js.map → agent-network-dashboard__next-internal_server_app_logs_page_actions_0xa2ox_.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_settings_networks_page_actions_09uw.sw.js.map → agent-network-dashboard__next-internal_server_app_messages_page_actions_10j3tg4.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_settings_page_actions_0mr68ai.js.map → agent-network-dashboard__next-internal_server_app_node_page_actions_0sl4ntd.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_settings_tokens_page_actions_0yi54x8.js.map → agent-network-dashboard__next-internal_server_app_nodes_page_actions_0cpgkfs.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_tasks_[id]_page_actions_0k_hm9g.js.map → agent-network-dashboard__next-internal_server_app_page_actions_08nuo36.js.map} +0 -0
- /package/.next/server/chunks/ssr/{_next-internal_server_app_tasks_page_actions_0_2o_cr.js.map → agent-network-dashboard__next-internal_server_app_settings_page_actions_0iv.lmk.js.map} +0 -0
- /package/.next/static/{q2pRPNeRoK1vOr1EX-qOG → zYWeA6KAnt4mH1Rredbmu}/_buildManifest.js +0 -0
- /package/.next/static/{q2pRPNeRoK1vOr1EX-qOG → zYWeA6KAnt4mH1Rredbmu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{q2pRPNeRoK1vOr1EX-qOG → zYWeA6KAnt4mH1Rredbmu}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { Session } from './types';
|
|
5
5
|
import { aliasAvatarColors, aliasInitial } from './AliasAvatar';
|
|
6
6
|
import { ChatPopover } from './ChatPopover';
|
|
@@ -16,6 +16,9 @@ interface MessageFlow {
|
|
|
16
16
|
interface TopoGraphProps {
|
|
17
17
|
sessions: Session[];
|
|
18
18
|
sseSessions: Record<string, number>;
|
|
19
|
+
// #84: a node.renamed event from the Overview's SSE listener. When the
|
|
20
|
+
// currently open chat popover targets `from`, it follows the rename to `to`.
|
|
21
|
+
renameSignal?: { from: string; to: string; ts: number } | null;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
interface Point {
|
|
@@ -29,6 +32,9 @@ interface FlowLink {
|
|
|
29
32
|
to: string;
|
|
30
33
|
count: number;
|
|
31
34
|
content: string;
|
|
35
|
+
/** ISO timestamp of the most recent message on this edge — drives
|
|
36
|
+
* the Round 10 freshness fade so dormant links recede visually. */
|
|
37
|
+
last_at: string;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
const cx = 500;
|
|
@@ -82,36 +88,52 @@ function truncate(value: string, max: number) {
|
|
|
82
88
|
return value.length > max ? `${value.slice(0, max - 1)}...` : value;
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
/** Round 12 / Loop: status trio audit.
|
|
92
|
+
*
|
|
93
|
+
* Each (status × theme) cell returns a {primary, halo, text} trio. The trio
|
|
94
|
+
* invariant — keep it when adding states or tweaking shades:
|
|
95
|
+
*
|
|
96
|
+
* light: primary = <hue>-600 halo = <hue>-100 * text = <hue>-800/900
|
|
97
|
+
* dark: primary = <hue>-400/500 halo = <hue>-900 text = <hue>-100
|
|
98
|
+
*
|
|
99
|
+
* * offline.halo light deviates intentionally to slate-200 (#e2e8f0):
|
|
100
|
+
* slate-100 (#f1f5f9) is too close to the panel bg (#f8fafc) and the
|
|
101
|
+
* halo would vanish. The other three rows keep the 100-shade.
|
|
102
|
+
*
|
|
103
|
+
* Audit caught one cross-family drift before this round: online-other halo
|
|
104
|
+
* light was #dbeafe (blue-100) while its primary (#0284c7 sky-600) and text
|
|
105
|
+
* (#0c4a6e sky-900) were sky-family — now sky-100 (#e0f2fe). Tiny visual
|
|
106
|
+
* difference; large hygiene win — every trio is now mono-hue. */
|
|
85
107
|
function nodeStatus(session: Session, isOnline: boolean, isLight: boolean) {
|
|
86
108
|
if (!isOnline) {
|
|
87
109
|
return {
|
|
88
110
|
label: 'offline',
|
|
89
|
-
primary: isLight ? '#94a3b8' : '#6b7280',
|
|
90
|
-
halo: isLight ? '#e2e8f0' : '#111827',
|
|
91
|
-
text: isLight ? '#475569' : '#9ca3af',
|
|
111
|
+
primary: isLight ? '#94a3b8' : '#6b7280', // slate-400 / gray-500
|
|
112
|
+
halo: isLight ? '#e2e8f0' : '#111827', // slate-200* / gray-900
|
|
113
|
+
text: isLight ? '#475569' : '#9ca3af', // slate-600 / gray-400
|
|
92
114
|
};
|
|
93
115
|
}
|
|
94
116
|
if (session.status === 'working') {
|
|
95
117
|
return {
|
|
96
118
|
label: 'working',
|
|
97
|
-
primary: isLight ? '#059669' : '#22c55e',
|
|
98
|
-
halo: isLight ? '#d1fae5' : '#14532d',
|
|
99
|
-
text: isLight ? '#065f46' : '#dcfce7',
|
|
119
|
+
primary: isLight ? '#059669' : '#22c55e', // emerald-600 / green-500
|
|
120
|
+
halo: isLight ? '#d1fae5' : '#14532d', // emerald-100 / green-900
|
|
121
|
+
text: isLight ? '#065f46' : '#dcfce7', // emerald-800 / green-100
|
|
100
122
|
};
|
|
101
123
|
}
|
|
102
124
|
if (session.status === 'idle') {
|
|
103
125
|
return {
|
|
104
126
|
label: 'idle',
|
|
105
|
-
primary: isLight ? '#0d9488' : '#2dd4bf',
|
|
106
|
-
halo: isLight ? '#ccfbf1' : '#134e4a',
|
|
107
|
-
text: isLight ? '#115e59' : '#ccfbf1',
|
|
127
|
+
primary: isLight ? '#0d9488' : '#2dd4bf', // teal-600 / teal-400
|
|
128
|
+
halo: isLight ? '#ccfbf1' : '#134e4a', // teal-100 / teal-900
|
|
129
|
+
text: isLight ? '#115e59' : '#ccfbf1', // teal-800 / teal-100
|
|
108
130
|
};
|
|
109
131
|
}
|
|
110
132
|
return {
|
|
111
133
|
label: session.status || 'online',
|
|
112
|
-
primary: isLight ? '#0284c7' : '#38bdf8',
|
|
113
|
-
halo: isLight ? '#
|
|
114
|
-
text: isLight ? '#0c4a6e' : '#e0f2fe',
|
|
134
|
+
primary: isLight ? '#0284c7' : '#38bdf8', // sky-600 / sky-400
|
|
135
|
+
halo: isLight ? '#e0f2fe' : '#0c4a6e', // sky-100 / sky-900 (was blue-100 — drift fixed Round 12)
|
|
136
|
+
text: isLight ? '#0c4a6e' : '#e0f2fe', // sky-900 / sky-100
|
|
115
137
|
};
|
|
116
138
|
}
|
|
117
139
|
|
|
@@ -237,22 +259,59 @@ function commonPrefix(a: string, b: string): string {
|
|
|
237
259
|
return a.slice(0, i);
|
|
238
260
|
}
|
|
239
261
|
|
|
240
|
-
|
|
241
|
-
|
|
262
|
+
/** #83 + #111: group nodes that share a ≥2-char alias prefix OR a project_dir
|
|
263
|
+
* ("either criterion → same group", Vincent 4724). Union-find over the
|
|
264
|
+
* sessions; the component label prefers the shared project_dir's basename,
|
|
265
|
+
* else the common alias prefix. Returns alias → groupKey. */
|
|
266
|
+
function computeGroups(sessions: { alias: string; project_dir?: string | null }[]): Record<string, string> {
|
|
267
|
+
const n = sessions.length;
|
|
268
|
+
const parent = sessions.map((_, i) => i);
|
|
269
|
+
const find = (i: number): number => {
|
|
270
|
+
while (parent[i] !== i) { parent[i] = parent[parent[i]]; i = parent[i]; }
|
|
271
|
+
return i;
|
|
272
|
+
};
|
|
273
|
+
const union = (a: number, b: number) => {
|
|
274
|
+
const ra = find(a), rb = find(b);
|
|
275
|
+
if (ra !== rb) parent[ra] = rb;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// union by shared project_dir
|
|
279
|
+
const byDir: Record<string, number[]> = {};
|
|
280
|
+
sessions.forEach((s, i) => {
|
|
281
|
+
const d = s.project_dir?.trim();
|
|
282
|
+
if (d) (byDir[d] ||= []).push(i);
|
|
283
|
+
});
|
|
284
|
+
for (const idxs of Object.values(byDir)) {
|
|
285
|
+
for (let k = 1; k < idxs.length; k++) union(idxs[0], idxs[k]);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// union by shared ≥2-char alias prefix — sort, link adjacent pairs
|
|
289
|
+
const order = sessions.map((_, i) => i).sort((a, b) => sessions[a].alias.localeCompare(sessions[b].alias));
|
|
290
|
+
for (let k = 0; k + 1 < order.length; k++) {
|
|
291
|
+
if (commonPrefix(sessions[order[k]].alias, sessions[order[k + 1]].alias).length >= 2) {
|
|
292
|
+
union(order[k], order[k + 1]);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// label each connected component
|
|
297
|
+
const comps: Record<number, number[]> = {};
|
|
298
|
+
for (let i = 0; i < n; i++) (comps[find(i)] ||= []).push(i);
|
|
242
299
|
const keys: Record<string, string> = {};
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
300
|
+
for (const members of Object.values(comps)) {
|
|
301
|
+
let label: string;
|
|
302
|
+
if (members.length === 1) {
|
|
303
|
+
label = sessions[members[0]].alias;
|
|
304
|
+
} else {
|
|
305
|
+
const dirs = new Set(members.map(i => sessions[i].project_dir?.trim()).filter(Boolean) as string[]);
|
|
306
|
+
if (dirs.size === 1) {
|
|
307
|
+
const d = [...dirs][0];
|
|
308
|
+
label = d.split('/').filter(Boolean).pop() || d;
|
|
309
|
+
} else {
|
|
310
|
+
label = members.map(i => sessions[i].alias).reduce((a, b) => commonPrefix(a, b));
|
|
311
|
+
if (label.length < 2) label = sessions[members[0]].alias;
|
|
312
|
+
}
|
|
252
313
|
}
|
|
253
|
-
const
|
|
254
|
-
for (let k = i; k <= j; k++) keys[sorted[k]] = key;
|
|
255
|
-
i = j + 1;
|
|
314
|
+
for (const i of members) keys[sessions[i].alias] = label;
|
|
256
315
|
}
|
|
257
316
|
return keys;
|
|
258
317
|
}
|
|
@@ -272,19 +331,27 @@ function buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point
|
|
|
272
331
|
const key = `${message.from_alias}->${message.to_alias}`;
|
|
273
332
|
const current = links.get(key);
|
|
274
333
|
|
|
334
|
+
// Keep the most-recent timestamp per pair so the render can fade
|
|
335
|
+
// dormant edges (Round 10 freshness fade).
|
|
336
|
+
const incoming = message.created_at || '';
|
|
337
|
+
const last_at = !current
|
|
338
|
+
? incoming
|
|
339
|
+
: (incoming > current.last_at ? incoming : current.last_at);
|
|
340
|
+
|
|
275
341
|
links.set(key, {
|
|
276
342
|
key,
|
|
277
343
|
from: message.from_alias,
|
|
278
344
|
to: message.to_alias,
|
|
279
345
|
count: (current?.count || 0) + 1,
|
|
280
346
|
content: current?.content || message.content,
|
|
347
|
+
last_at,
|
|
281
348
|
});
|
|
282
349
|
});
|
|
283
350
|
|
|
284
351
|
return [...links.values()].slice(0, 18);
|
|
285
352
|
}
|
|
286
353
|
|
|
287
|
-
export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
354
|
+
export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProps) {
|
|
288
355
|
const theme = useTheme();
|
|
289
356
|
const isLight = theme === 'light';
|
|
290
357
|
const pal = isLight ? LIGHT_PALETTE : DARK_PALETTE;
|
|
@@ -307,6 +374,20 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
307
374
|
try { localStorage.setItem('anet-topo-layout', next); } catch {}
|
|
308
375
|
return next;
|
|
309
376
|
});
|
|
377
|
+
// Issue #113: node size scale (Vincent 4727 — nodes too big / crowded at
|
|
378
|
+
// ~22 nodes). S/M/L → 0.7/0.84/1.0; default M (one notch down from the old
|
|
379
|
+
// fixed size). Persisted like the layout toggle.
|
|
380
|
+
const [nodeScale, setNodeScale] = useState(0.84);
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
try {
|
|
383
|
+
const saved = parseFloat(localStorage.getItem('anet-topo-nodescale') || '');
|
|
384
|
+
if (saved === 0.7 || saved === 0.84 || saved === 1) setNodeScale(saved);
|
|
385
|
+
} catch {}
|
|
386
|
+
}, []);
|
|
387
|
+
const pickNodeScale = (v: number) => {
|
|
388
|
+
setNodeScale(v);
|
|
389
|
+
try { localStorage.setItem('anet-topo-nodescale', String(v)); } catch {}
|
|
390
|
+
};
|
|
310
391
|
|
|
311
392
|
useEffect(() => {
|
|
312
393
|
const fetchMessages = async () => {
|
|
@@ -333,6 +414,8 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
333
414
|
flowLinks,
|
|
334
415
|
activeAliases,
|
|
335
416
|
groupKeys,
|
|
417
|
+
groupBoxes,
|
|
418
|
+
gridContentBottom,
|
|
336
419
|
} = useMemo(() => {
|
|
337
420
|
const sseCount = (s: { alias: string; network_id?: string }) =>
|
|
338
421
|
(s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];
|
|
@@ -342,36 +425,148 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
342
425
|
// index, so contiguous-in-array becomes contiguous-in-ring, i.e.
|
|
343
426
|
// each team visually clusters. localeCompare keeps CJK ordering sane.
|
|
344
427
|
const byAlias = (a: Session, b: Session) => a.alias.localeCompare(b.alias);
|
|
428
|
+
// #112 umbrella: ghost age-out — an offline node not seen recently is
|
|
429
|
+
// almost certainly a deleted node the server `/api/status` still
|
|
430
|
+
// returns (#74 root cause is server-side; this is the dashboard-side
|
|
431
|
+
// fallback so stale ghosts stop cluttering the topology). A missing
|
|
432
|
+
// last_seen_at is kept (conservative — could be a legitimately new node).
|
|
433
|
+
// Round 27 / P0: the 24h threshold was too lenient — Vincent's preview.29
|
|
434
|
+
// screenshot showed B站马 nodes deleted ~4 h earlier still visible. A
|
|
435
|
+
// healthy agent heartbeats every few seconds; if it's been silent for
|
|
436
|
+
// an hour it's effectively gone. 1 h gives a fresh disconnect time to
|
|
437
|
+
// come back while removing dead nodes well within an operator session.
|
|
438
|
+
const GHOST_MS = 60 * 60 * 1000;
|
|
439
|
+
const now = Date.now();
|
|
440
|
+
const isGhost = (s: Session) => {
|
|
441
|
+
if (s.status !== 'offline' || sseCount(s) || !s.last_seen_at) return false;
|
|
442
|
+
const t = Date.parse(s.last_seen_at);
|
|
443
|
+
return !Number.isNaN(t) && now - t > GHOST_MS;
|
|
444
|
+
};
|
|
345
445
|
const online = sessions.filter(s => s.status !== 'offline' || sseCount(s)).sort(byAlias);
|
|
346
|
-
const offline = sessions.filter(s => s.status === 'offline' && !sseCount(s)).sort(byAlias);
|
|
446
|
+
const offline = sessions.filter(s => s.status === 'offline' && !sseCount(s) && !isGhost(s)).sort(byAlias);
|
|
347
447
|
const positions: Record<string, Point> = {};
|
|
348
448
|
|
|
349
449
|
if (layout === 'grid') {
|
|
350
|
-
// Issue #87:
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
//
|
|
450
|
+
// Issue #87 + #111: group-banded grid. Nodes are sorted by alias so
|
|
451
|
+
// same-prefix aliases are adjacent; each multi-member prefix group then
|
|
452
|
+
// gets its OWN row(s) starting at column 0, while singletons pack into
|
|
453
|
+
// shared rows. Group-banded placement keeps every group's bounding box
|
|
454
|
+
// (#111) a clean rectangle — no box ever overlaps another group's.
|
|
354
455
|
const all = [...online, ...offline];
|
|
355
|
-
const
|
|
356
|
-
const cols = Math.max(1, Math.ceil(Math.sqrt(
|
|
357
|
-
|
|
358
|
-
|
|
456
|
+
const groupKeys = computeGroups(all);
|
|
457
|
+
const cols = Math.max(1, Math.ceil(Math.sqrt(all.length)));
|
|
458
|
+
// #112: gy0 starts below the (now compact) recent-signal / legend
|
|
459
|
+
// overlay panels — their max bottom edge is y≈112 — so grid row 0 is
|
|
460
|
+
// never occluded; gy1 extends near the canvas bottom for breathing room.
|
|
461
|
+
const gx0 = 150, gx1 = 850, gy0 = 126, gy1 = 652;
|
|
359
462
|
const cellW = (gx1 - gx0) / cols;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
463
|
+
// largest node radius at the current size scale — drives the group
|
|
464
|
+
// label band + the row-height floor so nothing ever overlaps.
|
|
465
|
+
const nodeR = Math.round(26 * nodeScale);
|
|
466
|
+
|
|
467
|
+
// ordered runs of consecutive same-group-key nodes (≥2 = real group)
|
|
468
|
+
const runs: { key: string; members: Session[] }[] = [];
|
|
469
|
+
for (const s of all) {
|
|
470
|
+
const gk = groupKeys[s.alias];
|
|
471
|
+
const last = runs[runs.length - 1];
|
|
472
|
+
if (last && last.key === gk) last.members.push(s);
|
|
473
|
+
else runs.push({ key: gk, members: [s] });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Pass 1 — assign each run to a band: a multi-member group owns its
|
|
477
|
+
// rows (left-aligned, so its bounding box is a tidy rect); contiguous
|
|
478
|
+
// singletons pack into shared rows (centred). Collect total row count.
|
|
479
|
+
type Band = { members: Session[]; startRow: number; centred: boolean; isGroup: boolean };
|
|
480
|
+
const bands: Band[] = [];
|
|
481
|
+
let row = 0;
|
|
482
|
+
let i = 0;
|
|
483
|
+
while (i < runs.length) {
|
|
484
|
+
if (runs[i].members.length >= 2) {
|
|
485
|
+
bands.push({ members: runs[i].members, startRow: row, centred: false, isGroup: true });
|
|
486
|
+
row += Math.ceil(runs[i].members.length / cols);
|
|
487
|
+
i++;
|
|
488
|
+
} else {
|
|
489
|
+
const singles: Session[] = [];
|
|
490
|
+
while (i < runs.length && runs[i].members.length < 2) {
|
|
491
|
+
singles.push(runs[i].members[0]);
|
|
492
|
+
i++;
|
|
493
|
+
}
|
|
494
|
+
bands.push({ members: singles, startRow: row, centred: true, isGroup: false });
|
|
495
|
+
row += Math.ceil(singles.length / cols);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const totalRows = Math.max(1, row);
|
|
499
|
+
// #112: the group label sits in a band ABOVE the topmost node, so the
|
|
500
|
+
// band must clear the node radius — GROUP_TOP is node-relative, never
|
|
501
|
+
// cellH-derived (cellH-derived was the label↔node overlap bug Vincent
|
|
502
|
+
// hit). cellH then floors at GROUP_TOP + 30 so the label band + bottom
|
|
503
|
+
// padding always fit and stacked group boxes never touch; past the
|
|
504
|
+
// floor the grid overflows and zoom/pan handles it.
|
|
505
|
+
const GROUP_TOP = nodeR + 20;
|
|
506
|
+
// Round 27 / P0 (Vincent screenshot preview.29): dense plain-text
|
|
507
|
+
// node labels (`pos.y + radius + denseDrop`, with a 3 px containerBg
|
|
508
|
+
// stroke halo for readability) at the BOTTOM row of band N would
|
|
509
|
+
// paint OVER the start of band N+1's group label, creating the
|
|
510
|
+
// "blueleap → eleap", "agent-network-dashboard → t-network / board"
|
|
511
|
+
// visual chopping. Geometry of the collision:
|
|
512
|
+
// dense label visual bottom = node_N.y + radius + denseDrop + halo
|
|
513
|
+
// group label glyph top = boxY + 4 (text y=boxY+14, ~10px ascent)
|
|
514
|
+
// = node_N+1.y - GROUP_TOP + 4
|
|
515
|
+
// node_N+1.y - node_N.y = cellH for consecutive rows, so no-collide:
|
|
516
|
+
// cellH ≥ radius + denseDrop + halo + GROUP_TOP - 4 + buffer
|
|
517
|
+
// With halo=3 buffer=4: cellH ≥ nodeR + denseDrop + GROUP_TOP + 3.
|
|
518
|
+
const denseDrop = nodeScale < 0.8 ? 12 : 14;
|
|
519
|
+
const cellH = Math.max(
|
|
520
|
+
2 * nodeR + 22, // node + dense label within band
|
|
521
|
+
GROUP_TOP + 12, // group-label band + box padding
|
|
522
|
+
nodeR + denseDrop + GROUP_TOP + 3, // round-27 dense↔group label clearance
|
|
523
|
+
Math.min(100, (gy1 - gy0) / totalRows),
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// Pass 2 — place each band's members.
|
|
527
|
+
for (const band of bands) {
|
|
528
|
+
band.members.forEach((s, idx) => {
|
|
529
|
+
const rowInBand = Math.floor(idx / cols);
|
|
530
|
+
const c = idx % cols;
|
|
531
|
+
const inRow = Math.min(cols, band.members.length - rowInBand * cols);
|
|
532
|
+
const inset = band.centred ? ((cols - inRow) * cellW) / 2 : 0;
|
|
533
|
+
positions[s.alias] = {
|
|
534
|
+
x: gx0 + inset + (c + 0.5) * cellW,
|
|
535
|
+
y: gy0 + (band.startRow + rowInBand + 0.5) * cellH,
|
|
536
|
+
};
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
371
540
|
const links = buildFlowLinks(messages, positions);
|
|
372
541
|
const active = new Set<string>();
|
|
373
542
|
links.forEach(link => { active.add(link.from); active.add(link.to); });
|
|
374
|
-
|
|
543
|
+
// #111: one bounding box per multi-member group (Vincent 4722). Each
|
|
544
|
+
// group owns its rows; GROUP_PAD fills the row space left below the
|
|
545
|
+
// nodes after the label band, so stacked group boxes always have a
|
|
546
|
+
// gap between them (GROUP_TOP is defined above, with cellH).
|
|
547
|
+
const GROUP_PAD = Math.max(8, Math.min(26, cellH - GROUP_TOP - 8)); // side/bottom
|
|
548
|
+
const groupBoxes = bands
|
|
549
|
+
.filter(b => b.isGroup)
|
|
550
|
+
.map(band => {
|
|
551
|
+
const pts = band.members.map(s => positions[s.alias]).filter(Boolean);
|
|
552
|
+
const xs = pts.map(p => p.x);
|
|
553
|
+
const ys = pts.map(p => p.y);
|
|
554
|
+
const minX = Math.min(...xs), minY = Math.min(...ys);
|
|
555
|
+
return {
|
|
556
|
+
key: band.members.length ? groupKeys[band.members[0].alias] : '',
|
|
557
|
+
count: band.members.length,
|
|
558
|
+
x: minX - GROUP_PAD,
|
|
559
|
+
y: minY - GROUP_TOP,
|
|
560
|
+
w: Math.max(...xs) - minX + GROUP_PAD * 2,
|
|
561
|
+
h: Math.max(...ys) - minY + GROUP_TOP + GROUP_PAD,
|
|
562
|
+
};
|
|
563
|
+
});
|
|
564
|
+
// Round 28 / Loop: surface the grid's natural content bottom so the
|
|
565
|
+
// mount effect can auto-fit zoom when the layout would overflow the
|
|
566
|
+
// viewBox. = bottom of the last node row + its label drop + a small
|
|
567
|
+
// breathing buffer. Round 27's cellH bump makes this overflow more
|
|
568
|
+
// common (30-node fleets reach ~774 px, viewBox is 680).
|
|
569
|
+
const gridContentBottom = gy0 + totalRows * cellH + 8;
|
|
375
570
|
return {
|
|
376
571
|
onlineNodes: online,
|
|
377
572
|
offlineNodes: offline,
|
|
@@ -379,6 +574,8 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
379
574
|
flowLinks: links,
|
|
380
575
|
activeAliases: active,
|
|
381
576
|
groupKeys,
|
|
577
|
+
groupBoxes,
|
|
578
|
+
gridContentBottom,
|
|
382
579
|
};
|
|
383
580
|
}
|
|
384
581
|
|
|
@@ -449,7 +646,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
449
646
|
});
|
|
450
647
|
|
|
451
648
|
// Round 106 (issue #83): group key per alias → shared hue per team.
|
|
452
|
-
const groupKeys =
|
|
649
|
+
const groupKeys = computeGroups([...online, ...offline]);
|
|
453
650
|
|
|
454
651
|
return {
|
|
455
652
|
onlineNodes: online,
|
|
@@ -458,15 +655,38 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
458
655
|
flowLinks: links,
|
|
459
656
|
activeAliases: active,
|
|
460
657
|
groupKeys,
|
|
658
|
+
// #111: group boxes are a grid-layout feature only — radially scattered
|
|
659
|
+
// ring nodes can't be cleanly boxed. Ring keeps the #83 prefix hue.
|
|
660
|
+
groupBoxes: [] as { key: string; count: number; x: number; y: number; w: number; h: number }[],
|
|
661
|
+
// ring fits within VIEWBOX_H by construction (offlineRadius=325 + centre at y=330)
|
|
662
|
+
gridContentBottom: 0,
|
|
461
663
|
};
|
|
462
|
-
}, [messages, sessions, sseSessions, layout]);
|
|
664
|
+
}, [messages, sessions, sseSessions, layout, nodeScale]);
|
|
463
665
|
|
|
464
666
|
const workingCount = onlineNodes.filter(s => s.status === 'working').length;
|
|
667
|
+
// Round 6 / Loop: vendor distribution for the header chip — at a glance
|
|
668
|
+
// "what's in the fleet" (A:5 M:2 O:8 书:12 …) without opening a node.
|
|
669
|
+
// Sorted by count desc; "unknown" vendors collapse into a "?" bucket.
|
|
670
|
+
const vendorDist = useMemo(() => {
|
|
671
|
+
const tally = new Map<string, { initial: string; count: number; color: string }>();
|
|
672
|
+
for (const s of [...onlineNodes, ...offlineNodes]) {
|
|
673
|
+
const v = vendorForModel(s.model);
|
|
674
|
+
const key = v.id === 'unknown' ? '?' : v.initial;
|
|
675
|
+
const cur = tally.get(key);
|
|
676
|
+
if (cur) cur.count++;
|
|
677
|
+
else tally.set(key, { initial: key, count: 1, color: v.mono.text });
|
|
678
|
+
}
|
|
679
|
+
return [...tally.values()].sort((a, b) => b.count - a.count);
|
|
680
|
+
}, [onlineNodes, offlineNodes]);
|
|
465
681
|
// Round 109 (Vincent 4582 P0): hover-gated labels above this node count
|
|
466
682
|
// so dense fleets show clean avatars instead of a wall of overlapping
|
|
467
683
|
// label cards. 16 ≈ where the triple-tier rings start to crowd.
|
|
468
684
|
const denseLayout = onlineNodes.length + offlineNodes.length > 16;
|
|
469
685
|
const [hoveredAlias, setHoveredAlias] = useState<string | null>(null);
|
|
686
|
+
// Round 8 / Loop: which group is currently focused. Nodes outside this
|
|
687
|
+
// group + other group boxes fade so the eye locks onto the team you're
|
|
688
|
+
// pointing at. Singletons use their own alias as the group key.
|
|
689
|
+
const hoveredGroup = hoveredAlias ? (groupKeys[hoveredAlias] ?? hoveredAlias) : null;
|
|
470
690
|
|
|
471
691
|
// --- Round 103 (issue #81): fullscreen + zoom + pan interaction layer ---
|
|
472
692
|
// DIY native (no d3 / svg-pan-zoom): wrap the topology content in a single
|
|
@@ -486,6 +706,25 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
486
706
|
// Issue #100: singleton chat popover. One alias at a time — clicking
|
|
487
707
|
// another node swaps the target and the conversation switches in place.
|
|
488
708
|
const [chatAlias, setChatAlias] = useState<string | null>(null);
|
|
709
|
+
// Round 14 / Loop: one-shot click ripple — fades outward from the clicked
|
|
710
|
+
// node so the click registers physically before the popover snaps in.
|
|
711
|
+
// Pairs with the Round 11 chat-focus ring: ripple expands, ring locks on.
|
|
712
|
+
// Keyed by ts so re-clicking the same node remounts the <circle> and the
|
|
713
|
+
// SMIL <animate> replays. Cleared 600ms after click (longer than the
|
|
714
|
+
// 500ms animation so a final frame at opacity 0 is still in the tree).
|
|
715
|
+
const [clickRipple, setClickRipple] = useState<{
|
|
716
|
+
ts: number; x: number; y: number; r0: number; color: string;
|
|
717
|
+
} | null>(null);
|
|
718
|
+
// #84: when a node is renamed while its chat popover is open, follow the
|
|
719
|
+
// rename so the conversation keeps targeting a live alias.
|
|
720
|
+
useEffect(() => {
|
|
721
|
+
if (renameSignal && renameSignal.from === chatAlias) {
|
|
722
|
+
setChatAlias(renameSignal.to);
|
|
723
|
+
}
|
|
724
|
+
// chatAlias intentionally omitted — only react to a new rename signal,
|
|
725
|
+
// not to the user opening/closing the popover.
|
|
726
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
727
|
+
}, [renameSignal]);
|
|
489
728
|
|
|
490
729
|
useEffect(() => { viewRef.current = view; }, [view]);
|
|
491
730
|
|
|
@@ -506,6 +745,42 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
506
745
|
} catch {}
|
|
507
746
|
}, []);
|
|
508
747
|
|
|
748
|
+
// Round 28 / Loop: first-paint auto-fit. Round 27's cellH bump means
|
|
749
|
+
// dense grids (≥6-7 rows) overflow the 680-px viewBox at the natural
|
|
750
|
+
// 100% zoom — a new user sees the topology with its bottom rows
|
|
751
|
+
// clipped and no hint that there's more below. Auto-fit on first
|
|
752
|
+
// paint (and only if the user has no persisted view) sets the
|
|
753
|
+
// initial zoom so all content fits. Subsequent zoom changes persist
|
|
754
|
+
// and override the auto-fit on reload; explicit reset (0 key) still
|
|
755
|
+
// goes back to 100% so the gesture's "reset to natural size" semantic
|
|
756
|
+
// is preserved.
|
|
757
|
+
//
|
|
758
|
+
// Capture pre-mount persistence in a useState initializer — the
|
|
759
|
+
// existing `persist` effect (declared below) runs on first render
|
|
760
|
+
// with the default {1,0,0} view and writes it to localStorage before
|
|
761
|
+
// the auto-fit effect (deps on async-arriving sessions) gets a turn.
|
|
762
|
+
// Reading localStorage from the effect would see that write and
|
|
763
|
+
// skip the fit. The useState snapshot fires once, before any effects.
|
|
764
|
+
const [hadPersistedViewOnMount] = useState<boolean>(
|
|
765
|
+
() => typeof window !== 'undefined' && !!localStorage.getItem('anet-topo-view'),
|
|
766
|
+
);
|
|
767
|
+
const autoFitDoneRef = useRef(false);
|
|
768
|
+
useEffect(() => {
|
|
769
|
+
if (autoFitDoneRef.current) return;
|
|
770
|
+
if (hadPersistedViewOnMount) {
|
|
771
|
+
autoFitDoneRef.current = true;
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (layout !== 'grid' || sessions.length === 0 || !gridContentBottom) return;
|
|
775
|
+
if (gridContentBottom <= VIEWBOX_H) {
|
|
776
|
+
autoFitDoneRef.current = true; // no overflow → no fit needed
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const fitZoom = Math.max(ZOOM_MIN, Math.min(1, VIEWBOX_H / gridContentBottom));
|
|
780
|
+
setView({ zoom: fitZoom, x: 0, y: 0 });
|
|
781
|
+
autoFitDoneRef.current = true;
|
|
782
|
+
}, [layout, sessions.length, gridContentBottom, hadPersistedViewOnMount]);
|
|
783
|
+
|
|
509
784
|
// persist view
|
|
510
785
|
useEffect(() => {
|
|
511
786
|
try { localStorage.setItem('anet-topo-view', JSON.stringify(view)); } catch {}
|
|
@@ -525,6 +800,14 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
525
800
|
const svg = svgRef.current;
|
|
526
801
|
if (!svg) return;
|
|
527
802
|
const onWheel = (e: WheelEvent) => {
|
|
803
|
+
// Round 23 / Loop: only zoom on Ctrl/Meta+wheel when inline; plain
|
|
804
|
+
// wheel over the canvas keeps scrolling the page (the topo sits at
|
|
805
|
+
// the top of /; trapping page scroll mid-page is the classic
|
|
806
|
+
// "scroll-jail" anti-pattern). In fullscreen the canvas owns the
|
|
807
|
+
// viewport so any wheel zooms — no page scroll to preserve. Pinch-
|
|
808
|
+
// zoom on a trackpad surfaces as ctrlKey=true natively, which is
|
|
809
|
+
// also what we want (zoom the canvas, not the browser).
|
|
810
|
+
if (!isFullscreen && !e.ctrlKey && !e.metaKey) return;
|
|
528
811
|
e.preventDefault();
|
|
529
812
|
const rect = svg.getBoundingClientRect();
|
|
530
813
|
const mx = ((e.clientX - rect.left) / rect.width) * VIEWBOX_W;
|
|
@@ -545,8 +828,17 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
545
828
|
};
|
|
546
829
|
svg.addEventListener('wheel', onWheel, { passive: false });
|
|
547
830
|
return () => svg.removeEventListener('wheel', onWheel);
|
|
548
|
-
|
|
549
|
-
|
|
831
|
+
// Re-attach when the fullscreen flag toggles so the handler's
|
|
832
|
+
// closure picks up the new value (capture-by-closure means we'd
|
|
833
|
+
// otherwise read the stale boolean forever).
|
|
834
|
+
}, [isFullscreen]);
|
|
835
|
+
|
|
836
|
+
// Round 21 / Loop: pan-aware cursor. Static `grab` gave no tactile cue
|
|
837
|
+
// that the canvas was actually being dragged — "grabbing" while
|
|
838
|
+
// dragRef.current.active = true tells the hand it's moving the
|
|
839
|
+
// viewport. Mirroring dragRef.active into state is the only way to
|
|
840
|
+
// re-render the SVG style; the ref itself doesn't trigger renders.
|
|
841
|
+
const [isPanning, setIsPanning] = useState(false);
|
|
550
842
|
const onPointerDown = (e: React.PointerEvent<SVGSVGElement>) => {
|
|
551
843
|
if (e.button !== 0) return;
|
|
552
844
|
(e.currentTarget as Element).setPointerCapture?.(e.pointerId);
|
|
@@ -557,6 +849,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
557
849
|
baseX: viewRef.current.x,
|
|
558
850
|
baseY: viewRef.current.y,
|
|
559
851
|
};
|
|
852
|
+
setIsPanning(true);
|
|
560
853
|
};
|
|
561
854
|
const onPointerMove = (e: React.PointerEvent<SVGSVGElement>) => {
|
|
562
855
|
const d = dragRef.current;
|
|
@@ -571,6 +864,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
571
864
|
const onPointerUp = (e: React.PointerEvent<SVGSVGElement>) => {
|
|
572
865
|
if (!dragRef.current.active) return;
|
|
573
866
|
dragRef.current.active = false;
|
|
867
|
+
setIsPanning(false);
|
|
574
868
|
try {
|
|
575
869
|
(e.currentTarget as Element).releasePointerCapture?.(e.pointerId);
|
|
576
870
|
} catch {}
|
|
@@ -588,6 +882,48 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
588
882
|
};
|
|
589
883
|
const resetView = () => setView({ zoom: 1, x: 0, y: 0 });
|
|
590
884
|
|
|
885
|
+
// Round 29 / Loop: `f` = fit-to-content. Shared by the Round 28
|
|
886
|
+
// first-paint auto-fit effect and the keyboard handler so the math is
|
|
887
|
+
// in one place. When content already fits at natural zoom, this is
|
|
888
|
+
// effectively a "recenter" — `f` always lands on a known good view.
|
|
889
|
+
const fitView = useCallback(() => {
|
|
890
|
+
const zoom = !gridContentBottom || gridContentBottom <= VIEWBOX_H
|
|
891
|
+
? 1
|
|
892
|
+
: Math.max(ZOOM_MIN, Math.min(1, VIEWBOX_H / gridContentBottom));
|
|
893
|
+
setView({ zoom, x: 0, y: 0 });
|
|
894
|
+
}, [gridContentBottom]);
|
|
895
|
+
|
|
896
|
+
// Round 22 / Loop: keyboard zoom — +/= zoom in, - zoom out, 0 reset.
|
|
897
|
+
// Round 29 / Loop: +f to fit content.
|
|
898
|
+
// Listen on window so the user doesn't need to focus the SVG first,
|
|
899
|
+
// but only act when no text input has focus (otherwise typing "-" in
|
|
900
|
+
// a chat would zoom the topology — surprising and bad). Modifier keys
|
|
901
|
+
// pass through so Cmd/Ctrl combos still hit their owners. The button
|
|
902
|
+
// titles below document these shortcuts so users discover them.
|
|
903
|
+
useEffect(() => {
|
|
904
|
+
const onKey = (e: KeyboardEvent) => {
|
|
905
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
906
|
+
const ae = document.activeElement as HTMLElement | null;
|
|
907
|
+
if (ae) {
|
|
908
|
+
const tag = ae.tagName;
|
|
909
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || ae.isContentEditable) return;
|
|
910
|
+
}
|
|
911
|
+
if (e.key === '+' || e.key === '=') { zoomBy(1.2); e.preventDefault(); }
|
|
912
|
+
else if (e.key === '-' || e.key === '_') { zoomBy(1 / 1.2); e.preventDefault(); }
|
|
913
|
+
else if (e.key === '0') { resetView(); e.preventDefault(); }
|
|
914
|
+
else if (e.key === 'f' || e.key === 'F') { fitView(); e.preventDefault(); }
|
|
915
|
+
// Round 32 / Loop: `l` toggles ring|grid. The vim-style `g l` route
|
|
916
|
+
// (Audit Log) requires a preceding `g` within 1500ms; a bare `l`
|
|
917
|
+
// outside that window is free for topology use.
|
|
918
|
+
else if (e.key === 'l' || e.key === 'L') { toggleLayout(); e.preventDefault(); }
|
|
919
|
+
};
|
|
920
|
+
window.addEventListener('keydown', onKey);
|
|
921
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
922
|
+
// zoomBy / resetView are stable wrt setView callback; fitView changes
|
|
923
|
+
// with gridContentBottom so deps list catches it.
|
|
924
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
925
|
+
}, [fitView]);
|
|
926
|
+
|
|
591
927
|
const toggleFullscreen = () => {
|
|
592
928
|
const el = containerRef.current;
|
|
593
929
|
if (!el) return;
|
|
@@ -615,6 +951,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
615
951
|
<button
|
|
616
952
|
onClick={() => { if (layout !== 'ring') toggleLayout(); }}
|
|
617
953
|
aria-pressed={layout === 'ring'}
|
|
954
|
+
title="Ring layout (l to toggle)"
|
|
618
955
|
className={`px-2.5 py-1 transition-colors ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300' : 'text-gray-500 hover:text-gray-400'}`}
|
|
619
956
|
>
|
|
620
957
|
Ring
|
|
@@ -622,6 +959,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
622
959
|
<button
|
|
623
960
|
onClick={() => { if (layout !== 'grid') toggleLayout(); }}
|
|
624
961
|
aria-pressed={layout === 'grid'}
|
|
962
|
+
title="Grid layout (l to toggle)"
|
|
625
963
|
className={`px-2.5 py-1 border-l border-gray-500/25 transition-colors ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300' : 'text-gray-500 hover:text-gray-400'}`}
|
|
626
964
|
>
|
|
627
965
|
Grid
|
|
@@ -633,6 +971,48 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
633
971
|
<span className="px-2.5 py-1 rounded-md bg-cyan-500/10 text-cyan-300 border border-cyan-500/20">
|
|
634
972
|
{onlineNodes.length} online
|
|
635
973
|
</span>
|
|
974
|
+
{/* Round 31 / Loop: fleet-health pressure bar. The "X working /
|
|
975
|
+
Y online" chips already carry the raw counts; the bar lets
|
|
976
|
+
the eye get the working/idle/offline RATIO in one glance
|
|
977
|
+
without mental math. Stacked 3-segment chip, ~64 px wide. */}
|
|
978
|
+
{(() => {
|
|
979
|
+
const w = workingCount;
|
|
980
|
+
const i = onlineNodes.length - workingCount; // idle = online - working
|
|
981
|
+
const o = offlineNodes.length;
|
|
982
|
+
const total = w + i + o;
|
|
983
|
+
if (total === 0) return null;
|
|
984
|
+
const seg = (n: number, color: string, key: string) => {
|
|
985
|
+
if (n === 0) return null;
|
|
986
|
+
return <span key={key} style={{ width: `${(n / total) * 100}%`, background: color, height: '100%' }} />;
|
|
987
|
+
};
|
|
988
|
+
return (
|
|
989
|
+
<span
|
|
990
|
+
className="inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 font-mono"
|
|
991
|
+
title={`${w} working · ${i} idle · ${o} offline`}
|
|
992
|
+
data-fleet-pressure
|
|
993
|
+
>
|
|
994
|
+
<span className="text-[10px] tracking-wide">pressure</span>
|
|
995
|
+
<span className="inline-flex h-1.5 w-16 rounded-full overflow-hidden" style={{ background: 'rgb(75 85 99 / 0.25)' }}>
|
|
996
|
+
{seg(w, isLight ? '#059669' : '#22c55e', 'w')}
|
|
997
|
+
{seg(i, isLight ? '#0d9488' : '#2dd4bf', 'i')}
|
|
998
|
+
{seg(o, isLight ? '#94a3b8' : '#6b7280', 'o')}
|
|
999
|
+
</span>
|
|
1000
|
+
</span>
|
|
1001
|
+
);
|
|
1002
|
+
})()}
|
|
1003
|
+
{vendorDist.length > 1 && (
|
|
1004
|
+
<span
|
|
1005
|
+
className="inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 font-mono"
|
|
1006
|
+
title="Fleet by model vendor"
|
|
1007
|
+
>
|
|
1008
|
+
{vendorDist.map(v => (
|
|
1009
|
+
<span key={v.initial} className="inline-flex items-baseline gap-0.5">
|
|
1010
|
+
<span style={{ color: v.color }}>{v.initial}</span>
|
|
1011
|
+
<span className="text-gray-500">:{v.count}</span>
|
|
1012
|
+
</span>
|
|
1013
|
+
))}
|
|
1014
|
+
</span>
|
|
1015
|
+
)}
|
|
636
1016
|
<span className="px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20">
|
|
637
1017
|
{flowLinks.length} active links
|
|
638
1018
|
</span>
|
|
@@ -655,7 +1035,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
655
1035
|
onPointerMove={onPointerMove}
|
|
656
1036
|
onPointerUp={onPointerUp}
|
|
657
1037
|
onPointerLeave={onPointerUp}
|
|
658
|
-
style={{ cursor: 'grab', touchAction: 'none' }}
|
|
1038
|
+
style={{ cursor: isPanning ? 'grabbing' : 'grab', touchAction: 'none' }}
|
|
659
1039
|
>
|
|
660
1040
|
<defs>
|
|
661
1041
|
<linearGradient id="topo-panel" x1="0" x2="1" y1="0" y2="1">
|
|
@@ -677,9 +1057,37 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
677
1057
|
</feMerge>
|
|
678
1058
|
</filter>
|
|
679
1059
|
)}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1060
|
+
{/* Round 16 / Loop: 3-tier flow-link arrow markers.
|
|
1061
|
+
The single marker had `markerUnits` defaulting to
|
|
1062
|
+
`strokeWidth`, so heavy edges (stroke=7) rendered 35-user-
|
|
1063
|
+
unit arrowheads — visually dominant, the head outweighed
|
|
1064
|
+
the line. Switching to `userSpaceOnUse` decouples arrow
|
|
1065
|
+
size from stroke; binning by count gives a clearer
|
|
1066
|
+
hierarchy than continuous linear scaling:
|
|
1067
|
+
s (count 1-2) → 12 user units
|
|
1068
|
+
m (count 3-4) → 16 user units (alias for `topo-arrow`)
|
|
1069
|
+
l (count 5+) → 22 user units
|
|
1070
|
+
`topo-arrow` stays bound to the medium tier so the legend
|
|
1071
|
+
swatch (line ~1500) renders without change. */}
|
|
1072
|
+
{[
|
|
1073
|
+
{ id: 'topo-arrow-s', size: 12 },
|
|
1074
|
+
{ id: 'topo-arrow', size: 16 },
|
|
1075
|
+
{ id: 'topo-arrow-l', size: 22 },
|
|
1076
|
+
].map(m => (
|
|
1077
|
+
<marker
|
|
1078
|
+
key={m.id}
|
|
1079
|
+
id={m.id}
|
|
1080
|
+
viewBox="0 0 10 10"
|
|
1081
|
+
refX="8"
|
|
1082
|
+
refY="5"
|
|
1083
|
+
markerWidth={m.size}
|
|
1084
|
+
markerHeight={m.size}
|
|
1085
|
+
markerUnits="userSpaceOnUse"
|
|
1086
|
+
orient="auto-start-reverse"
|
|
1087
|
+
>
|
|
1088
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill={pal.arrowFill} />
|
|
1089
|
+
</marker>
|
|
1090
|
+
))}
|
|
683
1091
|
{/* Round 45: radial radar sweep gradient — bright at center
|
|
684
1092
|
(where it meets the hub) fading to leading edge. */}
|
|
685
1093
|
<radialGradient id="topo-sweep" cx="0%" cy="50%" r="100%">
|
|
@@ -807,16 +1215,89 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
807
1215
|
);
|
|
808
1216
|
})}
|
|
809
1217
|
|
|
1218
|
+
{/* #111: prefix-group boundary boxes (Vincent 4722). Grid layout
|
|
1219
|
+
only — groupBoxes is empty in ring mode. Rendered behind the
|
|
1220
|
+
flow links + nodes; pointer-events off so they never intercept
|
|
1221
|
+
a node click. Restrained dashed container + group-name label. */}
|
|
1222
|
+
{groupBoxes.map(box => {
|
|
1223
|
+
const isHovered = hoveredGroup === box.key;
|
|
1224
|
+
// Round 18 / Loop: group-box hover linkage. The Round 8 fade
|
|
1225
|
+
// already dropped OUT-of-focus groups to 0.28, but the IN-focus
|
|
1226
|
+
// group sat at its baseline appearance — no positive emphasis.
|
|
1227
|
+
// Hovering now upgrades the box to an "accent" treatment:
|
|
1228
|
+
// solid stroke (not dashed), thicker, accent-coloured; brighter
|
|
1229
|
+
// text and slightly stronger fill. Label and box read as one
|
|
1230
|
+
// selected unit. Geometry unchanged → overlap test untouched.
|
|
1231
|
+
return (
|
|
1232
|
+
<g
|
|
1233
|
+
key={`grp-${box.key}`}
|
|
1234
|
+
data-group={box.key}
|
|
1235
|
+
className="transition-opacity"
|
|
1236
|
+
style={{ pointerEvents: 'none', opacity: !hoveredGroup || isHovered ? 1 : 0.28 }}
|
|
1237
|
+
>
|
|
1238
|
+
<rect
|
|
1239
|
+
x={box.x}
|
|
1240
|
+
y={box.y}
|
|
1241
|
+
width={box.w}
|
|
1242
|
+
height={box.h}
|
|
1243
|
+
rx="14"
|
|
1244
|
+
fill={isLight ? '#0f172a' : '#a5b4fc'}
|
|
1245
|
+
fillOpacity={isHovered ? (isLight ? 0.05 : 0.09) : (isLight ? 0.025 : 0.045)}
|
|
1246
|
+
stroke={isHovered ? pal.legendAccent : pal.ringStroke}
|
|
1247
|
+
strokeWidth={isHovered ? 2 : 1.5}
|
|
1248
|
+
strokeDasharray={isHovered ? 'none' : '6 6'}
|
|
1249
|
+
style={{ transition: 'stroke 200ms ease-out, stroke-width 200ms ease-out, fill-opacity 200ms ease-out' }}
|
|
1250
|
+
/>
|
|
1251
|
+
<text
|
|
1252
|
+
x={box.x + 12}
|
|
1253
|
+
y={box.y + 14}
|
|
1254
|
+
fill={isHovered ? pal.legendHeadline : pal.legendText}
|
|
1255
|
+
fontSize="13"
|
|
1256
|
+
fontFamily="monospace"
|
|
1257
|
+
fontWeight="700"
|
|
1258
|
+
style={{ transition: 'fill 200ms ease-out' }}
|
|
1259
|
+
>
|
|
1260
|
+
{box.key}
|
|
1261
|
+
{/* Round 19 / Loop: member-count chip. Inline tspan stays
|
|
1262
|
+
in the single <text> bbox the overlap test reads, so
|
|
1263
|
+
the node↔label guard still catches if the chip ever
|
|
1264
|
+
pushes the label far enough right to clip a node.
|
|
1265
|
+
Smaller + lighter weight reads as metadata, not name. */}
|
|
1266
|
+
<tspan dx="6" fill={pal.legendText} fontSize="11" fontWeight="400">· {box.count}</tspan>
|
|
1267
|
+
</text>
|
|
1268
|
+
</g>
|
|
1269
|
+
);
|
|
1270
|
+
})}
|
|
1271
|
+
|
|
810
1272
|
{/* directed message flows */}
|
|
811
1273
|
{flowLinks.map((link, index) => {
|
|
812
1274
|
const from = nodePositions[link.from];
|
|
813
1275
|
const to = nodePositions[link.to];
|
|
814
1276
|
if (!from || !to) return null;
|
|
815
1277
|
|
|
816
|
-
|
|
1278
|
+
// Round 7 / Loop: lift now scales with distance so short links
|
|
1279
|
+
// aren't over-bent (long links keep the ~36px hump), and the
|
|
1280
|
+
// particle period shortens with link.count so busier edges
|
|
1281
|
+
// visibly pulse faster — instant info density on top of the
|
|
1282
|
+
// existing stroke-width-by-count chip.
|
|
1283
|
+
const dist = Math.hypot(to.x - from.x, to.y - from.y);
|
|
1284
|
+
const lift = (index % 2 === 0 ? 1 : -1) * Math.min(36, dist * 0.18);
|
|
817
1285
|
const path = curvePath(from, to, lift);
|
|
818
1286
|
const width = Math.min(2 + link.count, 7);
|
|
819
|
-
const duration = 2.
|
|
1287
|
+
const duration = Math.max(0.9, 2.6 / Math.sqrt(link.count));
|
|
1288
|
+
// Round 10 / Loop: freshness fade. An edge that fired ≤30s ago
|
|
1289
|
+
// stays at full intensity; over 5 minutes it decays to ~35%.
|
|
1290
|
+
// Surfaces "what's happening now" vs background chatter without
|
|
1291
|
+
// hiding old flow entirely (some context still useful). `now`
|
|
1292
|
+
// captured at useMemo-recompute time (every 5s message refresh)
|
|
1293
|
+
// — accuracy is within the poll interval, plenty.
|
|
1294
|
+
const ageMs = link.last_at ? Math.max(0, Date.now() - Date.parse(link.last_at)) : 0;
|
|
1295
|
+
const fresh = Math.max(0.35, 1 - ageMs / (5 * 60 * 1000));
|
|
1296
|
+
// Round 16 arrow-tier binning — keep `topo-arrow` as the
|
|
1297
|
+
// medium tier id so the legend swatch picks it up unchanged.
|
|
1298
|
+
const arrowId = link.count <= 2 ? 'topo-arrow-s'
|
|
1299
|
+
: link.count <= 4 ? 'topo-arrow'
|
|
1300
|
+
: 'topo-arrow-l';
|
|
820
1301
|
|
|
821
1302
|
return (
|
|
822
1303
|
<g key={link.key}>
|
|
@@ -825,9 +1306,10 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
825
1306
|
fill="none"
|
|
826
1307
|
stroke={pal.flowEdge}
|
|
827
1308
|
strokeWidth={width}
|
|
828
|
-
opacity={isLight ? 0.22 : 0.28}
|
|
1309
|
+
opacity={(isLight ? 0.22 : 0.28) * fresh}
|
|
829
1310
|
filter={isLight ? undefined : 'url(#topo-glow)'}
|
|
830
|
-
markerEnd=
|
|
1311
|
+
markerEnd={`url(#${arrowId})`}
|
|
1312
|
+
className="transition-opacity duration-500"
|
|
831
1313
|
/>
|
|
832
1314
|
<path
|
|
833
1315
|
id={`flow-path-${index}`}
|
|
@@ -836,37 +1318,37 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
836
1318
|
stroke={pal.flowPath}
|
|
837
1319
|
strokeWidth="1"
|
|
838
1320
|
strokeDasharray="2 12"
|
|
839
|
-
opacity={isLight ? 0.4 : 0.75}
|
|
1321
|
+
opacity={(isLight ? 0.4 : 0.75) * fresh}
|
|
1322
|
+
className="transition-opacity duration-500"
|
|
840
1323
|
/>
|
|
841
|
-
<circle r="4" fill={pal.flowParticle} filter={isLight ? undefined : 'url(#topo-glow)'}>
|
|
1324
|
+
<circle r="4" fill={pal.flowParticle} filter={isLight ? undefined : 'url(#topo-glow)'} opacity={fresh}>
|
|
842
1325
|
<animateMotion dur={`${duration}s`} repeatCount="indefinite" path={path} />
|
|
843
1326
|
</circle>
|
|
844
1327
|
</g>
|
|
845
1328
|
);
|
|
846
1329
|
})}
|
|
847
1330
|
|
|
848
|
-
{/* center hub — round 39
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
the
|
|
1331
|
+
{/* center hub — round 39 sized + round 13 restraint.
|
|
1332
|
+
The hub is the control-plane anchor. r39 gave it two outward
|
|
1333
|
+
pulses (r 10→38, 2.4s, double-phase) which read as "loudest
|
|
1334
|
+
node in the room" — wrong semantics: anchors don't emit,
|
|
1335
|
+
they hold. r13 swaps the kinetic pulse for a slow opacity
|
|
1336
|
+
breath on the static halo (4s, ±15% from base), so the hub
|
|
1337
|
+
stays alive without throwing kinetic energy outward. The
|
|
1338
|
+
dual-circle "lit lamp" core is unchanged. */}
|
|
854
1339
|
{layout === 'ring' && (<g>
|
|
855
|
-
{/*
|
|
1340
|
+
{/* grounding halo — now breathes in opacity, no expansion */}
|
|
856
1341
|
<circle
|
|
857
1342
|
cx={cx} cy={cy} r="18"
|
|
858
1343
|
fill={isLight ? '#d1fae5' : '#10b981'}
|
|
859
|
-
opacity={isLight ? 0.
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
<circle cx={cx} cy={cy} r="10" fill="none" stroke="#10b981" strokeWidth="1.5" opacity="0">
|
|
868
|
-
<animate attributeName="r" values="10;38;10" dur="2.4s" begin="1.2s" repeatCount="indefinite" />
|
|
869
|
-
<animate attributeName="opacity" values="0.45;0;0.45" dur="2.4s" begin="1.2s" repeatCount="indefinite" />
|
|
1344
|
+
opacity={isLight ? 0.42 : 0.12}
|
|
1345
|
+
>
|
|
1346
|
+
<animate
|
|
1347
|
+
attributeName="opacity"
|
|
1348
|
+
values={isLight ? '0.32;0.52;0.32' : '0.08;0.16;0.08'}
|
|
1349
|
+
dur="4s"
|
|
1350
|
+
repeatCount="indefinite"
|
|
1351
|
+
/>
|
|
870
1352
|
</circle>
|
|
871
1353
|
{/* core — 20px diameter, larger inner highlight reads as a "lit lamp" */}
|
|
872
1354
|
<circle cx={cx} cy={cy} r="10" fill={isLight ? '#059669' : '#10b981'} />
|
|
@@ -874,7 +1356,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
874
1356
|
</g>)}
|
|
875
1357
|
|
|
876
1358
|
{/* agent nodes */}
|
|
877
|
-
{[...onlineNodes, ...offlineNodes].map(session => {
|
|
1359
|
+
{[...onlineNodes, ...offlineNodes].map((session, nodeIdx) => {
|
|
878
1360
|
const pos = nodePositions[session.alias];
|
|
879
1361
|
if (!pos) return null;
|
|
880
1362
|
|
|
@@ -882,7 +1364,9 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
882
1364
|
const isOnline = session.status !== 'offline' || !!sseCountFor;
|
|
883
1365
|
const status = nodeStatus(session, isOnline, isLight);
|
|
884
1366
|
const isActive = activeAliases.has(session.alias);
|
|
885
|
-
|
|
1367
|
+
// #113: node size scales with the S/M/L control; halos, labels,
|
|
1368
|
+
// badge and avatar all derive from `radius` so they follow.
|
|
1369
|
+
const radius = Math.round((isOnline ? 26 : 18) * nodeScale);
|
|
886
1370
|
// Round 109/110 (Vincent 4582 + 4583 P0): at high node counts
|
|
887
1371
|
// the 100px opaque label cards overlapped each other and
|
|
888
1372
|
// covered neighbouring avatars. But hiding text entirely went
|
|
@@ -892,12 +1376,48 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
892
1376
|
// occlude an avatar), and the full card appears only for the
|
|
893
1377
|
// hovered node or once zoomed in past 1.4×.
|
|
894
1378
|
const showFullLabel = !denseLayout || hoveredAlias === session.alias || view.zoom >= 1.4;
|
|
1379
|
+
// Round 8: when a node in another group is hovered, fade this
|
|
1380
|
+
// one. Same-group nodes (incl. singletons matching the hovered
|
|
1381
|
+
// alias) stay full. Pure visual focus, geometry unchanged.
|
|
1382
|
+
const inFocus = !hoveredGroup || (groupKeys[session.alias] ?? session.alias) === hoveredGroup;
|
|
895
1383
|
|
|
896
1384
|
return (
|
|
897
1385
|
<g
|
|
898
1386
|
key={session.alias}
|
|
899
|
-
|
|
900
|
-
|
|
1387
|
+
data-node={session.alias}
|
|
1388
|
+
// Round 3 / Loop: `anet-fade-in` runs once when the <g>
|
|
1389
|
+
// mounts — a new session entering the fleet (or the topology
|
|
1390
|
+
// first rendering) eases in instead of popping. Re-renders of
|
|
1391
|
+
// an existing node don't re-trigger (React preserves the <g>
|
|
1392
|
+
// via the alias key), so status changes don't flicker. The
|
|
1393
|
+
// global prefers-reduced-motion sweep already neutralises it.
|
|
1394
|
+
className="group transition-opacity anet-fade-in"
|
|
1395
|
+
style={{
|
|
1396
|
+
cursor: 'pointer',
|
|
1397
|
+
// Round 17 / Loop: offline nodes drop to 0.6 at rest so
|
|
1398
|
+
// online nodes pop without losing the offline-as-ghost
|
|
1399
|
+
// information. The dashed stroke + smaller radius already
|
|
1400
|
+
// say "offline"; dimming the whole group strengthens the
|
|
1401
|
+
// online-vs-offline hierarchy at first glance. Exempt the
|
|
1402
|
+
// chat-focused node — if the user explicitly opened a
|
|
1403
|
+
// popover targeting an offline alias, that node stays
|
|
1404
|
+
// full-brightness so the focus ring + popover read as one
|
|
1405
|
+
// selected thing rather than a dimmed selection. Group-
|
|
1406
|
+
// hover fade (Round 8) still wins when inFocus is false.
|
|
1407
|
+
opacity: !inFocus
|
|
1408
|
+
? 0.32
|
|
1409
|
+
: chatAlias === session.alias
|
|
1410
|
+
? 1
|
|
1411
|
+
: isOnline ? 1 : 0.6,
|
|
1412
|
+
// Round 9 / Loop: stagger the anet-fade-in so the topology
|
|
1413
|
+
// reveals as a wave on first paint instead of one big pop.
|
|
1414
|
+
// Cap at 24 indices (≈600ms tail) so 50-node fleets still
|
|
1415
|
+
// finish revealing within a beat. CSS animation-delay only
|
|
1416
|
+
// applies during the keyframe — re-renders without a new
|
|
1417
|
+
// mount (same alias key) don't replay, so status changes
|
|
1418
|
+
// never trigger the stagger again.
|
|
1419
|
+
animationDelay: `${Math.min(nodeIdx, 24) * 25}ms`,
|
|
1420
|
+
}}
|
|
901
1421
|
// Stop the pointerdown from reaching the SVG pan handler: the
|
|
902
1422
|
// SVG calls setPointerCapture, and a captured pointer makes
|
|
903
1423
|
// Chromium fire the follow-up `click` on the SVG instead of
|
|
@@ -905,21 +1425,114 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
905
1425
|
// Side effect (intended): you pan from empty canvas, not by
|
|
906
1426
|
// grabbing a node.
|
|
907
1427
|
onPointerDown={(e) => e.stopPropagation()}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1428
|
+
// Track hover globally (not just under denseLayout) so the
|
|
1429
|
+
// Round 8 group-focus fade works at any fleet size.
|
|
1430
|
+
onPointerEnter={() => setHoveredAlias(session.alias)}
|
|
1431
|
+
onPointerLeave={() => setHoveredAlias(prev => (prev === session.alias ? null : prev))}
|
|
1432
|
+
onClick={() => {
|
|
1433
|
+
setChatAlias(session.alias);
|
|
1434
|
+
// Round 14 ripple — capture position, radius and status
|
|
1435
|
+
// colour at click time so the one-shot circle is self-
|
|
1436
|
+
// contained (no re-render-time recomputation needed).
|
|
1437
|
+
setClickRipple({
|
|
1438
|
+
ts: Date.now(),
|
|
1439
|
+
x: pos.x, y: pos.y, r0: radius,
|
|
1440
|
+
color: status.primary,
|
|
1441
|
+
});
|
|
1442
|
+
setTimeout(() => setClickRipple(prev =>
|
|
1443
|
+
prev && Date.now() - prev.ts >= 590 ? null : prev), 600);
|
|
1444
|
+
}}
|
|
911
1445
|
>
|
|
912
1446
|
{/* Issue #96: native hover tooltip — "Vendor · model · Runtime".
|
|
913
1447
|
Falls back to just the alias when the node reports no
|
|
914
|
-
model/runtime.
|
|
915
|
-
|
|
1448
|
+
model/runtime.
|
|
1449
|
+
Round 33 / Loop: cwd line answers "what is this agent on?".
|
|
1450
|
+
Round 34 / Loop: for offline nodes, append "last seen: 6m ago"
|
|
1451
|
+
so the operator knows whether to wait or chase. Online nodes
|
|
1452
|
+
skip the line (Round 27's 1 h ghost age-out means anything
|
|
1453
|
+
still online has heartbeated recently — the line would just
|
|
1454
|
+
be noise). Accepts both ISO ("…T06:00:28Z") and SQL-style
|
|
1455
|
+
("… 06:00:28" assumed UTC) formats. */}
|
|
1456
|
+
{(() => {
|
|
1457
|
+
const formatLastSeen = (dateStr: string): string | null => {
|
|
1458
|
+
if (!dateStr) return null;
|
|
1459
|
+
const iso = dateStr.includes('T') ? dateStr : dateStr.replace(' ', 'T') + 'Z';
|
|
1460
|
+
const t = Date.parse(iso);
|
|
1461
|
+
if (!Number.isFinite(t)) return null;
|
|
1462
|
+
const s = Math.floor((Date.now() - t) / 1000);
|
|
1463
|
+
if (s < 0) return 'just now';
|
|
1464
|
+
if (s < 60) return `${s}s ago`;
|
|
1465
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
1466
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
1467
|
+
return `${Math.floor(s / 86400)}d ago`;
|
|
1468
|
+
};
|
|
1469
|
+
const lastSeen = !isOnline && session.last_seen_at ? formatLastSeen(session.last_seen_at) : null;
|
|
1470
|
+
return (
|
|
1471
|
+
<title>{[
|
|
1472
|
+
identityLine(session.model, session.runtime) || session.alias,
|
|
1473
|
+
session.project_dir ? `cwd: ${session.project_dir}` : null,
|
|
1474
|
+
lastSeen ? `last seen: ${lastSeen}` : null,
|
|
1475
|
+
].filter(Boolean).join('\n')}</title>
|
|
1476
|
+
);
|
|
1477
|
+
})()}
|
|
1478
|
+
{/* Round 2 / Loop: hover ring — a thin outer stroke that fades
|
|
1479
|
+
in when the cursor enters the node, signalling clickability
|
|
1480
|
+
(real-user feedback for the chat-popover open). Pure CSS via
|
|
1481
|
+
Tailwind group-hover, so it costs nothing per frame and
|
|
1482
|
+
respects prefers-reduced-motion via the global media query. */}
|
|
1483
|
+
<circle
|
|
1484
|
+
cx={pos.x}
|
|
1485
|
+
cy={pos.y}
|
|
1486
|
+
r={radius + 12}
|
|
1487
|
+
fill="none"
|
|
1488
|
+
stroke={status.primary}
|
|
1489
|
+
// strokeWidth must NOT be 1.5 (offline status ring) or 3
|
|
1490
|
+
// (online status ring) — the overlap test selects by those
|
|
1491
|
+
// exact widths and would mis-count this invisible hover
|
|
1492
|
+
// ring as a node footprint.
|
|
1493
|
+
strokeWidth="2"
|
|
1494
|
+
className="opacity-0 group-hover:opacity-70 transition-opacity duration-150"
|
|
1495
|
+
style={{ pointerEvents: 'none' }}
|
|
1496
|
+
/>
|
|
1497
|
+
{/* Round 11 / Loop: chat-focus ring — when the ChatPopover is
|
|
1498
|
+
open targeting this node, anchor a persistent ring around
|
|
1499
|
+
it so the floating popover visibly links back to its source
|
|
1500
|
+
node. Static (not pulsing) so it reads as "selected state"
|
|
1501
|
+
rather than "this node is active". strokeWidth=2.5 stays
|
|
1502
|
+
clear of the overlap-test selectors (1.5 / 3). Sits just
|
|
1503
|
+
outside the halo radius+8 so it never overlaps a neighbour
|
|
1504
|
+
(halos already pack flush in dense grids). */}
|
|
1505
|
+
{chatAlias === session.alias && (
|
|
1506
|
+
<circle
|
|
1507
|
+
cx={pos.x}
|
|
1508
|
+
cy={pos.y}
|
|
1509
|
+
r={radius + 14}
|
|
1510
|
+
fill="none"
|
|
1511
|
+
stroke={status.primary}
|
|
1512
|
+
strokeWidth="2.5"
|
|
1513
|
+
opacity={isLight ? 0.85 : 0.95}
|
|
1514
|
+
filter={!isLight ? 'url(#topo-glow)' : undefined}
|
|
1515
|
+
className="transition-opacity duration-200"
|
|
1516
|
+
style={{ pointerEvents: 'none' }}
|
|
1517
|
+
/>
|
|
1518
|
+
)}
|
|
916
1519
|
{isActive && (
|
|
917
1520
|
<circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity={isLight ? 0.08 : 0.12}>
|
|
918
1521
|
<animate attributeName="r" values={`${radius + 8};${radius + 22};${radius + 8}`} dur="2.4s" repeatCount="indefinite" />
|
|
919
1522
|
<animate attributeName="opacity" values={isLight ? '0.12;0.02;0.12' : '0.18;0.04;0.18'} dur="2.4s" repeatCount="indefinite" />
|
|
920
1523
|
</circle>
|
|
921
1524
|
)}
|
|
922
|
-
|
|
1525
|
+
{/* Round 4 / Loop: transition-[fill,stroke,opacity] smooths
|
|
1526
|
+
status colour changes so idle↔working↔offline doesn't snap
|
|
1527
|
+
— task replies / node-rename / SSE updates ease in. */}
|
|
1528
|
+
<circle
|
|
1529
|
+
cx={pos.x}
|
|
1530
|
+
cy={pos.y}
|
|
1531
|
+
r={radius + 8}
|
|
1532
|
+
fill={status.halo}
|
|
1533
|
+
opacity={isOnline ? (isLight ? 0.85 : 0.65) : (isLight ? 0.4 : 0.25)}
|
|
1534
|
+
className="transition-[fill,opacity] duration-300 ease-out"
|
|
1535
|
+
/>
|
|
923
1536
|
<circle
|
|
924
1537
|
cx={pos.x}
|
|
925
1538
|
cy={pos.y}
|
|
@@ -929,6 +1542,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
929
1542
|
strokeWidth={isOnline ? 3 : 1.5}
|
|
930
1543
|
strokeDasharray={isOnline ? 'none' : '5 5'}
|
|
931
1544
|
filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}
|
|
1545
|
+
className="transition-[fill,stroke] duration-300 ease-out"
|
|
932
1546
|
/>
|
|
933
1547
|
{/* Issue #96: node "avatar" is now driven by the model
|
|
934
1548
|
vendor. Decision order:
|
|
@@ -941,7 +1555,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
941
1555
|
4. unknown vendor / null model → the prefix-group
|
|
942
1556
|
hue-hashed initial (#83/#99 behaviour, unchanged). */}
|
|
943
1557
|
{(() => {
|
|
944
|
-
const ar = isOnline ? 14 : 10;
|
|
1558
|
+
const ar = Math.round((isOnline ? 14 : 10) * nodeScale);
|
|
945
1559
|
const size = radius * 2;
|
|
946
1560
|
const vendor = vendorForModel(session.model);
|
|
947
1561
|
const internByAlias = /书生|书小生|intern/i.test(session.alias);
|
|
@@ -966,7 +1580,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
966
1580
|
<circle cx={pos.x} cy={pos.y} r={ar} fill={vendor.mono.bg} stroke={vendor.mono.ring} strokeWidth="1" />
|
|
967
1581
|
<text
|
|
968
1582
|
x={pos.x} y={pos.y} dy="0.34em" textAnchor="middle"
|
|
969
|
-
fill={vendor.mono.text} fontSize={
|
|
1583
|
+
fill={vendor.mono.text} fontSize={ar}
|
|
970
1584
|
fontFamily="monospace" fontWeight="700"
|
|
971
1585
|
>
|
|
972
1586
|
{vendor.initial}
|
|
@@ -986,7 +1600,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
986
1600
|
dy="0.34em"
|
|
987
1601
|
textAnchor="middle"
|
|
988
1602
|
fill={c.text}
|
|
989
|
-
fontSize={
|
|
1603
|
+
fontSize={ar}
|
|
990
1604
|
fontFamily="monospace"
|
|
991
1605
|
fontWeight="700"
|
|
992
1606
|
>
|
|
@@ -1015,78 +1629,203 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
1015
1629
|
</g>
|
|
1016
1630
|
);
|
|
1017
1631
|
})()}
|
|
1018
|
-
{session.status === 'working' && (
|
|
1019
|
-
//
|
|
1020
|
-
//
|
|
1021
|
-
//
|
|
1022
|
-
//
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1632
|
+
{session.status === 'working' && (() => {
|
|
1633
|
+
// Round 24 / Loop: pulse rate ↔ traffic. The dot's dur
|
|
1634
|
+
// was a flat 1.1s — every working node looked equally
|
|
1635
|
+
// busy. Mapping it to sse:N lets the eye land on what's
|
|
1636
|
+
// actually hot. 3 discrete tiers read cleaner than
|
|
1637
|
+
// continuous easing at a glance; thresholds picked so
|
|
1638
|
+
// typical 1-2-SSE Claude/Codex sessions stay in the
|
|
1639
|
+
// "active" tier and only multi-pane orchestrators pop
|
|
1640
|
+
// into the "busy" tier. Geometry unchanged (r=2.5).
|
|
1641
|
+
const sse = sseCountFor ?? 0;
|
|
1642
|
+
const dur = sse >= 4 ? '0.7s' : sse >= 2 ? '0.9s' : '1.2s';
|
|
1643
|
+
return (
|
|
1644
|
+
<circle cx={pos.x} cy={pos.y - (radius - 6)} r="2.5" fill={pal.flowParticle} data-pulse-dur={dur}>
|
|
1645
|
+
<animate attributeName="opacity" values="1;0.25;1" dur={dur} repeatCount="indefinite" />
|
|
1646
|
+
</circle>
|
|
1647
|
+
);
|
|
1648
|
+
})()}
|
|
1027
1649
|
|
|
1028
1650
|
{/* Round 98 (issue #61): label rect 124px → 100px.
|
|
1029
1651
|
Round 109/110 (Vincent P0): full opaque card below the
|
|
1030
1652
|
density threshold / on hover / when zoomed; otherwise a
|
|
1031
1653
|
lightweight plain-text alias that keeps every node
|
|
1032
|
-
labelled without an opaque box covering its neighbours.
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1654
|
+
labelled without an opaque box covering its neighbours.
|
|
1655
|
+
Round 15 / Loop: when nodeScale=S the node shrinks 30%
|
|
1656
|
+
but the label card was staying full-size, so the label
|
|
1657
|
+
visually outweighed the small node. Tighten the card
|
|
1658
|
+
frame, alias / sub fontSize, drop-offset and truncate
|
|
1659
|
+
length specifically for S; M and L keep their existing
|
|
1660
|
+
sizes (M ≈ L for labels by design — the S user is the
|
|
1661
|
+
one who explicitly asked for a denser view). */}
|
|
1662
|
+
{(() => {
|
|
1663
|
+
const isSmall = nodeScale < 0.8;
|
|
1664
|
+
const cardW = isSmall ? 88 : 100;
|
|
1665
|
+
const cardH = isSmall ? 36 : 42;
|
|
1666
|
+
const cardTopY = isSmall ? -12 : -14;
|
|
1667
|
+
const aliasFs = isSmall ? 11 : 12;
|
|
1668
|
+
const subFs = isSmall ? 8 : 9;
|
|
1669
|
+
const subY = isSmall ? 15 : 17;
|
|
1670
|
+
const dropY = isSmall ? 18 : 22;
|
|
1671
|
+
const fullMax = isSmall ? 11 : 12;
|
|
1672
|
+
const denseFs = isSmall ? 9 : 10;
|
|
1673
|
+
const denseDrop = isSmall ? 12 : 14;
|
|
1674
|
+
// Round 26 / Loop: micro-lift the label group on hover —
|
|
1675
|
+
// 1.5 px upward, 200 ms ease. Pairs with the Round 18
|
|
1676
|
+
// group-box hover-accent treatment to give the same
|
|
1677
|
+
// "this is the focused element" feedback at the per-
|
|
1678
|
+
// node level. CSS transform stacks onto the SVG
|
|
1679
|
+
// positioning transform attribute (SVG 2 cascade);
|
|
1680
|
+
// bbox is unchanged at rest, so the overlap-test gate
|
|
1681
|
+
// continues to see the geometric layout it expects.
|
|
1682
|
+
return showFullLabel ? (
|
|
1683
|
+
<g transform={`translate(${pos.x}, ${pos.y + radius + dropY})`} style={{ pointerEvents: 'none' }}
|
|
1684
|
+
className="transition-transform duration-200 group-hover:-translate-y-[1.5px]">
|
|
1685
|
+
<rect x={-cardW / 2} y={cardTopY} width={cardW} height={cardH} rx="6" fill={pal.labelBox.fill} stroke={pal.labelBox.stroke} opacity={isLight ? 1 : 0.94} />
|
|
1686
|
+
<text x="0" y="1" textAnchor="middle" fill={status.text} fontSize={aliasFs} fontFamily="monospace" fontWeight="700">
|
|
1687
|
+
{truncate(session.alias, fullMax)}
|
|
1688
|
+
</text>
|
|
1689
|
+
<text x="0" y={subY} textAnchor="middle" fill={status.primary} fontSize={subFs} fontFamily="monospace">
|
|
1690
|
+
{status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}
|
|
1691
|
+
</text>
|
|
1692
|
+
</g>
|
|
1693
|
+
) : (
|
|
1694
|
+
<text
|
|
1695
|
+
x={pos.x}
|
|
1696
|
+
y={pos.y + radius + denseDrop}
|
|
1697
|
+
textAnchor="middle"
|
|
1698
|
+
fill={status.text}
|
|
1699
|
+
fontSize={denseFs}
|
|
1700
|
+
fontFamily="monospace"
|
|
1701
|
+
fontWeight="700"
|
|
1702
|
+
opacity={0.9}
|
|
1703
|
+
className="transition-transform duration-200 group-hover:-translate-y-[1.5px]"
|
|
1704
|
+
style={{ pointerEvents: 'none', paintOrder: 'stroke' }}
|
|
1705
|
+
stroke={pal.containerBg}
|
|
1706
|
+
strokeWidth="3"
|
|
1707
|
+
>
|
|
1708
|
+
{truncate(session.alias, isSmall ? 9 : 10)}
|
|
1041
1709
|
</text>
|
|
1042
|
-
|
|
1043
|
-
)
|
|
1044
|
-
<text
|
|
1045
|
-
x={pos.x}
|
|
1046
|
-
y={pos.y + radius + 14}
|
|
1047
|
-
textAnchor="middle"
|
|
1048
|
-
fill={status.text}
|
|
1049
|
-
fontSize="10"
|
|
1050
|
-
fontFamily="monospace"
|
|
1051
|
-
fontWeight="700"
|
|
1052
|
-
opacity={0.9}
|
|
1053
|
-
style={{ pointerEvents: 'none', paintOrder: 'stroke' }}
|
|
1054
|
-
stroke={pal.containerBg}
|
|
1055
|
-
strokeWidth="3"
|
|
1056
|
-
>
|
|
1057
|
-
{truncate(session.alias, 10)}
|
|
1058
|
-
</text>
|
|
1059
|
-
)}
|
|
1710
|
+
);
|
|
1711
|
+
})()}
|
|
1060
1712
|
</g>
|
|
1061
1713
|
);
|
|
1062
1714
|
})}
|
|
1063
1715
|
|
|
1716
|
+
{/* Round 14 / Loop: click ripple — one-shot expanding ring from
|
|
1717
|
+
the clicked node, ~500ms, status-coloured. Sits inside the
|
|
1718
|
+
zoom/pan <g> so it scales / pans with the topology. Keyed by
|
|
1719
|
+
ts so a re-click on any node (same or different) remounts the
|
|
1720
|
+
<circle> and the SMIL <animate> elements replay from t=0.
|
|
1721
|
+
strokeWidth=2 doesn't match the overlap-test selectors. */}
|
|
1722
|
+
{clickRipple && (
|
|
1723
|
+
<circle
|
|
1724
|
+
key={clickRipple.ts}
|
|
1725
|
+
cx={clickRipple.x}
|
|
1726
|
+
cy={clickRipple.y}
|
|
1727
|
+
r={clickRipple.r0 + 4}
|
|
1728
|
+
fill="none"
|
|
1729
|
+
stroke={clickRipple.color}
|
|
1730
|
+
strokeWidth="2"
|
|
1731
|
+
opacity="0"
|
|
1732
|
+
style={{ pointerEvents: 'none' }}
|
|
1733
|
+
>
|
|
1734
|
+
<animate attributeName="r" values={`${clickRipple.r0 + 4};${clickRipple.r0 + 30}`} dur="0.5s" fill="freeze" />
|
|
1735
|
+
<animate attributeName="opacity" values="0.7;0" dur="0.5s" fill="freeze" />
|
|
1736
|
+
</circle>
|
|
1737
|
+
)}
|
|
1738
|
+
|
|
1739
|
+
</g>
|
|
1740
|
+
|
|
1741
|
+
{/* #112: overlay panels (recent-signal + legend) render OUTSIDE the
|
|
1742
|
+
zoom/pan <g> so they stay fixed while the topology pans/zooms.
|
|
1743
|
+
They're sized + tucked into the top corners so every corner of
|
|
1744
|
+
each panel is >325px from the canvas centre — i.e. fully outside
|
|
1745
|
+
the outermost (offline) ring. No node on any ring can reach the
|
|
1746
|
+
corner triangles, so the panels never overlap a node, in ring
|
|
1747
|
+
OR grid layout (Vincent 4727 zero-overlap criterion). */}
|
|
1064
1748
|
{/* latest flow labels */}
|
|
1065
|
-
<g transform="translate(
|
|
1066
|
-
<rect x="0" y="0" width="
|
|
1067
|
-
<text x="
|
|
1068
|
-
<text x="
|
|
1069
|
-
{flowLinks.slice(0,
|
|
1070
|
-
<text key={link.key} x="
|
|
1071
|
-
{truncate(link.from,
|
|
1749
|
+
<g transform="translate(16, 16)">
|
|
1750
|
+
<rect x="0" y="0" width="230" height="84" rx="10" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 0.97 : 0.92} />
|
|
1751
|
+
<text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight="700">recent signal</text>
|
|
1752
|
+
<text x="150" y="21" fill={pal.legendAccent} fontSize="10" fontFamily="monospace">{messages.length} msgs</text>
|
|
1753
|
+
{flowLinks.slice(0, 3).map((link, index) => (
|
|
1754
|
+
<text key={link.key} x="13" y={38 + index * 16} fill={pal.legendText} fontSize="9" fontFamily="monospace">
|
|
1755
|
+
{truncate(link.from, 6)} {'->'} {truncate(link.to, 6)} / {link.count} / {truncate(link.content, 12)}
|
|
1072
1756
|
</text>
|
|
1073
1757
|
))}
|
|
1074
1758
|
</g>
|
|
1075
1759
|
|
|
1076
1760
|
{/* legend */}
|
|
1077
|
-
<g transform="translate(
|
|
1078
|
-
<rect x="0" y="0" width="
|
|
1079
|
-
<circle cx="
|
|
1080
|
-
<text x="
|
|
1081
|
-
<circle cx="
|
|
1082
|
-
<text x="
|
|
1083
|
-
<circle cx="
|
|
1084
|
-
<text x="
|
|
1085
|
-
<path d="
|
|
1086
|
-
</g>
|
|
1761
|
+
<g transform="translate(760, 16)">
|
|
1762
|
+
<rect x="0" y="0" width="224" height="96" rx="10" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 0.97 : 0.92} />
|
|
1763
|
+
<circle cx="16" cy="24" r="5.5" fill={isLight ? '#059669' : '#22c55e'} />
|
|
1764
|
+
<text x="30" y="28" fill={pal.legendText} fontSize="11" fontFamily="monospace">working node</text>
|
|
1765
|
+
<circle cx="16" cy="48" r="5.5" fill={isLight ? '#0d9488' : '#2dd4bf'} />
|
|
1766
|
+
<text x="30" y="52" fill={pal.legendText} fontSize="11" fontFamily="monospace">online idle</text>
|
|
1767
|
+
<circle cx="16" cy="72" r="5.5" fill={isLight ? '#94a3b8' : '#6b7280'} />
|
|
1768
|
+
<text x="30" y="76" fill={pal.legendText} fontSize="11" fontFamily="monospace">offline / no SSE</text>
|
|
1769
|
+
<path d="M140,72 Q164,48 196,72" fill="none" stroke={pal.flowEdge} strokeWidth="3" markerEnd="url(#topo-arrow)" />
|
|
1087
1770
|
</g>
|
|
1088
1771
|
</svg>
|
|
1089
1772
|
|
|
1773
|
+
{/* Round 30 / Loop: minimap. Big fleets in fullscreen mode at high
|
|
1774
|
+
zoom let users lose their position — the minimap shows the
|
|
1775
|
+
whole topology miniaturised plus a viewport rectangle so the
|
|
1776
|
+
user always knows where they are. Click anywhere to recenter
|
|
1777
|
+
the canvas there. Only mounted when the view is non-default
|
|
1778
|
+
(zoomed or panned) since at 1× centered the minimap and the
|
|
1779
|
+
canvas show the same thing. HTML overlay so it stays fixed
|
|
1780
|
+
while the SVG transforms. */}
|
|
1781
|
+
{(() => {
|
|
1782
|
+
const isDefaultView = Math.abs(view.zoom - 1) < 0.01 && Math.abs(view.x) < 1 && Math.abs(view.y) < 1;
|
|
1783
|
+
if (isDefaultView || (onlineNodes.length + offlineNodes.length) === 0) return null;
|
|
1784
|
+
const MW = 120, MH = 82;
|
|
1785
|
+
const sx = MW / VIEWBOX_W, sy = MH / VIEWBOX_H;
|
|
1786
|
+
const rectX = (-view.x / view.zoom) * sx;
|
|
1787
|
+
const rectY = (-view.y / view.zoom) * sy;
|
|
1788
|
+
const rectW = (VIEWBOX_W / view.zoom) * sx;
|
|
1789
|
+
const rectH = (VIEWBOX_H / view.zoom) * sy;
|
|
1790
|
+
return (
|
|
1791
|
+
<div
|
|
1792
|
+
className="absolute right-3 rounded-md border shadow-lg shadow-black/30 overflow-hidden anet-fade-in"
|
|
1793
|
+
style={{ bottom: 56, background: pal.legendBox.fill, borderColor: pal.containerBorder, cursor: 'crosshair' }}
|
|
1794
|
+
role="img"
|
|
1795
|
+
aria-label="Topology minimap — click to recenter"
|
|
1796
|
+
title="Minimap · click to recenter"
|
|
1797
|
+
onClick={(e) => {
|
|
1798
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
1799
|
+
const fx = (e.clientX - r.left) / r.width;
|
|
1800
|
+
const fy = (e.clientY - r.top) / r.height;
|
|
1801
|
+
setView(prev => ({
|
|
1802
|
+
...prev,
|
|
1803
|
+
x: VIEWBOX_W / 2 - fx * VIEWBOX_W * prev.zoom,
|
|
1804
|
+
y: VIEWBOX_H / 2 - fy * VIEWBOX_H * prev.zoom,
|
|
1805
|
+
}));
|
|
1806
|
+
}}
|
|
1807
|
+
data-topo-minimap
|
|
1808
|
+
>
|
|
1809
|
+
<svg width={MW} height={MH} viewBox={`0 0 ${MW} ${MH}`} style={{ display: 'block' }}>
|
|
1810
|
+
{[...onlineNodes, ...offlineNodes].map(s => {
|
|
1811
|
+
const p = nodePositions[s.alias];
|
|
1812
|
+
if (!p) return null;
|
|
1813
|
+
const sseN = (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];
|
|
1814
|
+
const isOn = s.status !== 'offline' || !!sseN;
|
|
1815
|
+
const st = nodeStatus(s, isOn, isLight);
|
|
1816
|
+
return <circle key={s.alias} cx={p.x * sx} cy={p.y * sy} r={isOn ? 1.7 : 1.2} fill={st.primary} opacity={isOn ? 0.9 : 0.5} />;
|
|
1817
|
+
})}
|
|
1818
|
+
{/* viewport rectangle */}
|
|
1819
|
+
<rect
|
|
1820
|
+
x={Math.max(0, rectX)} y={Math.max(0, rectY)}
|
|
1821
|
+
width={Math.max(0, Math.min(MW - Math.max(0, rectX), rectW))}
|
|
1822
|
+
height={Math.max(0, Math.min(MH - Math.max(0, rectY), rectH))}
|
|
1823
|
+
fill="none" stroke={pal.legendAccent} strokeWidth="1" opacity="0.9"
|
|
1824
|
+
/>
|
|
1825
|
+
</svg>
|
|
1826
|
+
</div>
|
|
1827
|
+
);
|
|
1828
|
+
})()}
|
|
1090
1829
|
{/* Round 103 (issue #81): zoom / pan / fullscreen controls — HTML
|
|
1091
1830
|
overlay so they stay fixed while the SVG content transforms.
|
|
1092
1831
|
Round 104: Vincent 实测 — the reset action used to be hidden
|
|
@@ -1094,6 +1833,26 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
1094
1833
|
Split into a plain % readout + an explicit reset button with
|
|
1095
1834
|
its own icon + tooltip. */}
|
|
1096
1835
|
<div className="absolute bottom-3 right-3 flex items-center gap-1.5 text-xs select-none">
|
|
1836
|
+
{/* #113: node size — S / M / L segmented control (Vincent 4727). */}
|
|
1837
|
+
<div
|
|
1838
|
+
className="flex items-center rounded-md border overflow-hidden"
|
|
1839
|
+
style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder }}
|
|
1840
|
+
role="group"
|
|
1841
|
+
aria-label="Node size"
|
|
1842
|
+
>
|
|
1843
|
+
{([['S', 0.7], ['M', 0.84], ['L', 1]] as const).map(([lbl, v], idx) => (
|
|
1844
|
+
<button
|
|
1845
|
+
key={lbl}
|
|
1846
|
+
onClick={() => pickNodeScale(v)}
|
|
1847
|
+
aria-pressed={nodeScale === v}
|
|
1848
|
+
title={`Node size: ${lbl === 'S' ? 'small' : lbl === 'M' ? 'medium' : 'large'}`}
|
|
1849
|
+
className={`px-2 py-1 transition-colors ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300' : 'hover:bg-white/5'}`}
|
|
1850
|
+
style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}
|
|
1851
|
+
>
|
|
1852
|
+
{lbl}
|
|
1853
|
+
</button>
|
|
1854
|
+
))}
|
|
1855
|
+
</div>
|
|
1097
1856
|
<div
|
|
1098
1857
|
className="flex items-center rounded-md border overflow-hidden"
|
|
1099
1858
|
style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder }}
|
|
@@ -1103,6 +1862,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
1103
1862
|
className="px-2 py-1 hover:bg-white/5 transition-colors"
|
|
1104
1863
|
style={{ color: pal.legendText }}
|
|
1105
1864
|
aria-label="Zoom out"
|
|
1865
|
+
title="Zoom out (−)"
|
|
1106
1866
|
>
|
|
1107
1867
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" aria-hidden><path d="M5 12h14" /></svg>
|
|
1108
1868
|
</button>
|
|
@@ -1118,6 +1878,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
1118
1878
|
className="px-2 py-1 hover:bg-white/5 transition-colors"
|
|
1119
1879
|
style={{ color: pal.legendText }}
|
|
1120
1880
|
aria-label="Zoom in"
|
|
1881
|
+
title="Zoom in (+)"
|
|
1121
1882
|
>
|
|
1122
1883
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" aria-hidden><path d="M12 5v14M5 12h14" /></svg>
|
|
1123
1884
|
</button>
|
|
@@ -1127,7 +1888,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
|
|
|
1127
1888
|
className="p-1.5 rounded-md border hover:bg-white/5 transition-colors"
|
|
1128
1889
|
style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}
|
|
1129
1890
|
aria-label="Reset view"
|
|
1130
|
-
title="Reset zoom + pan (or double-click the canvas)"
|
|
1891
|
+
title="Reset zoom + pan (0, or double-click the canvas)"
|
|
1131
1892
|
>
|
|
1132
1893
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
|
|
1133
1894
|
<path d="M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.4 2.6L3 8" />
|