@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/dist/components/actions/ActionCard.js +2 -2
  2. package/dist/components/actions/ActionCard.js.map +1 -1
  3. package/dist/components/actions/ActionTypeBadge.js +1 -1
  4. package/dist/components/actions/ActionTypeBadge.js.map +1 -1
  5. package/dist/components/actions/ExecutionHistory.js +1 -1
  6. package/dist/components/actions/ExecutionHistory.js.map +1 -1
  7. package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
  8. package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
  9. package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
  10. package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
  11. package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
  12. package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
  13. package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
  14. package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
  15. package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
  16. package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
  17. package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
  18. package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
  19. package/dist/components/agent-manager/AgentManagerTab.js +1 -1
  20. package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
  21. package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
  22. package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
  23. package/dist/components/agent-manager/BridleConfigPanel.js +24 -24
  24. package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
  25. package/dist/components/agent-manager/PluginInstallModal.js +1 -1
  26. package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
  27. package/dist/components/agent-manager/ProfilesPanel.js +5 -5
  28. package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
  29. package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
  30. package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
  31. package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
  32. package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
  33. package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
  34. package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
  35. package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
  36. package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
  37. package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
  38. package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
  39. package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
  40. package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
  41. package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
  42. package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
  43. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
  44. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
  45. package/dist/components/agents/AgentCard.js +14 -11
  46. package/dist/components/agents/AgentCard.js.map +1 -1
  47. package/dist/components/ai/AIContextsTab.js +1 -1
  48. package/dist/components/ai/AIContextsTab.js.map +1 -1
  49. package/dist/components/ai/AIOverviewTab.js +4 -4
  50. package/dist/components/ai/AIOverviewTab.js.map +1 -1
  51. package/dist/components/ai/AIPlanPanel.js +4 -4
  52. package/dist/components/ai/AIPlanPanel.js.map +1 -1
  53. package/dist/components/ai/AIPlaygroundTab.js +1 -1
  54. package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
  55. package/dist/components/ai/AIPromptsTab.js +2 -2
  56. package/dist/components/ai/AIPromptsTab.js.map +1 -1
  57. package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
  58. package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
  59. package/dist/components/ai/AIQueueTab.js +4 -4
  60. package/dist/components/ai/AIQueueTab.js.map +1 -1
  61. package/dist/components/ai/AISessionsTab.js +4 -4
  62. package/dist/components/ai/AISessionsTab.js.map +1 -1
  63. package/dist/components/ai/AITasksTab.js +1 -1
  64. package/dist/components/ai/AITasksTab.js.map +1 -1
  65. package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
  66. package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
  67. package/dist/components/ai/PluginHarnessPicker.js +3 -3
  68. package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
  69. package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
  70. package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
  71. package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
  72. package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
  73. package/dist/components/audit/AuditDeleteDialog.js +2 -2
  74. package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
  75. package/dist/components/audit/AuditExportButton.js +5 -5
  76. package/dist/components/audit/AuditExportButton.js.map +1 -1
  77. package/dist/components/audit/AuditLogDetail.js +9 -9
  78. package/dist/components/audit/AuditLogDetail.js.map +1 -1
  79. package/dist/components/audit/AuditLogFilters.js +1 -1
  80. package/dist/components/audit/AuditLogFilters.js.map +1 -1
  81. package/dist/components/audit/AuditLogStats.js +1 -1
  82. package/dist/components/audit/AuditLogStats.js.map +1 -1
  83. package/dist/components/audit/AuditLogTable.js +28 -28
  84. package/dist/components/audit/AuditLogTable.js.map +1 -1
  85. package/dist/components/catalog/CatalogCard.js +3 -3
  86. package/dist/components/catalog/CatalogCard.js.map +1 -1
  87. package/dist/components/catalog/CatalogForm.js +1 -1
  88. package/dist/components/catalog/CatalogForm.js.map +1 -1
  89. package/dist/components/common/ActionAgentTargetNotice.js +6 -6
  90. package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
  91. package/dist/components/common/CommandEditor.js +1 -1
  92. package/dist/components/common/CommandEditor.js.map +1 -1
  93. package/dist/components/docs/DocsPageTree.js +1 -1
  94. package/dist/components/docs/DocsPageTree.js.map +1 -1
  95. package/dist/components/docs/DocsPreviewPanel.js +1 -1
  96. package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
  97. package/dist/components/docs/DocsRevisionHistory.js +4 -4
  98. package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
  99. package/dist/components/docs/DocsSettingsPanel.js +7 -7
  100. package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
  101. package/dist/components/docs/DocsTabPanel.js +1 -1
  102. package/dist/components/docs/DocsTabPanel.js.map +1 -1
  103. package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
  104. package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
  105. package/dist/components/graph/MeshDeployWizard.js +3 -3
  106. package/dist/components/graph/MeshDeployWizard.js.map +1 -1
  107. package/dist/components/graph/MeshRequestPanel.js +2 -2
  108. package/dist/components/graph/MeshRequestPanel.js.map +1 -1
  109. package/dist/components/idp-components/ComponentCard.js +5 -5
  110. package/dist/components/idp-components/ComponentCard.js.map +1 -1
  111. package/dist/components/idp-components/ComponentForm.js +1 -1
  112. package/dist/components/idp-components/ComponentForm.js.map +1 -1
  113. package/dist/components/idp-components/DependencyGraphTab.js +7 -7
  114. package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
  115. package/dist/components/logs/LogViewer.js +14 -14
  116. package/dist/components/logs/LogViewer.js.map +1 -1
  117. package/dist/components/notes/NoteCard.js +1 -1
  118. package/dist/components/notes/NoteCard.js.map +1 -1
  119. package/dist/components/notes/NoteEditor.js +2 -2
  120. package/dist/components/notes/NoteEditor.js.map +1 -1
  121. package/dist/components/notes/NoteList.js +1 -1
  122. package/dist/components/notes/NoteList.js.map +1 -1
  123. package/dist/components/notes/NotesTabPanel.js +1 -1
  124. package/dist/components/notes/NotesTabPanel.js.map +1 -1
  125. package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
  126. package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
  127. package/dist/components/scorecard/ScorecardPanel.js +3 -3
  128. package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
  129. package/dist/components/security/SecurityScanList.js +11 -11
  130. package/dist/components/security/SecurityScanList.js.map +1 -1
  131. package/dist/components/sessions/SessionCard.js +26 -19
  132. package/dist/components/sessions/SessionCard.js.map +1 -1
  133. package/dist/components/sessions/SessionStatusBadge.js +7 -7
  134. package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
  135. package/dist/components/shared/AgentSelector.js +4 -4
  136. package/dist/components/shared/AgentSelector.js.map +1 -1
  137. package/dist/components/shared/AgentSetupBanner.js +4 -4
  138. package/dist/components/shared/AgentSetupBanner.js.map +1 -1
  139. package/dist/components/shared/Card.js +4 -4
  140. package/dist/components/shared/Card.js.map +1 -1
  141. package/dist/components/shared/ErrorState.js +15 -9
  142. package/dist/components/shared/ErrorState.js.map +1 -1
  143. package/dist/components/shared/FilterDropdown.js +2 -2
  144. package/dist/components/shared/FilterDropdown.js.map +1 -1
  145. package/dist/components/shared/PageSkeleton.js +6 -6
  146. package/dist/components/shared/PageSkeleton.js.map +1 -1
  147. package/dist/components/shared/SearchInput.js +1 -1
  148. package/dist/components/shared/SearchInput.js.map +1 -1
  149. package/dist/components/shared/StatCard.js +7 -7
  150. package/dist/components/shared/StatCard.js.map +1 -1
  151. package/dist/components/templates/PublishTemplateForm.js +2 -2
  152. package/dist/components/templates/PublishTemplateForm.js.map +1 -1
  153. package/dist/components/templates/TemplateCard.js +5 -5
  154. package/dist/components/templates/TemplateCard.js.map +1 -1
  155. package/dist/components/templates/TemplateWizard.js +2 -2
  156. package/dist/components/templates/TemplateWizard.js.map +1 -1
  157. package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
  158. package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
  159. package/dist/components/tunnels/ProviderBadge.js +1 -1
  160. package/dist/components/tunnels/ProviderBadge.js.map +1 -1
  161. package/dist/components/tunnels/TunnelCard.js +16 -16
  162. package/dist/components/tunnels/TunnelCard.js.map +1 -1
  163. package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
  164. package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
  165. package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
  166. package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
  167. package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
  168. package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
  169. package/dist/components/vibecalendar/LogsDrawer.js +2 -2
  170. package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
  171. package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
  172. package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
  173. package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
  174. package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
  175. package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
  176. package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
  177. package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
  178. package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
  179. package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
  180. package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
  181. package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
  182. package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
  183. package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
  184. package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
  185. package/dist/components/vibecalendar/types.js +7 -7
  186. package/dist/components/vibecalendar/types.js.map +1 -1
  187. package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
  188. package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
  189. package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
  190. package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
  191. package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
  192. package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
  193. package/dist/components/vibedeck/VibeDeckForm.js +8 -8
  194. package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
  195. package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
  196. package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
  197. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
  198. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
  199. package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
  200. package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
  201. package/dist/components/vibes/VibeCard.js +22 -18
  202. package/dist/components/vibes/VibeCard.js.map +1 -1
  203. package/dist/components/vibes/VibeList.js +16 -12
  204. package/dist/components/vibes/VibeList.js.map +1 -1
  205. package/dist/pages/ActionsPage.js +4 -3
  206. package/dist/pages/ActionsPage.js.map +1 -1
  207. package/dist/pages/AgentDetailsPage.js +9 -9
  208. package/dist/pages/AgentDetailsPage.js.map +1 -1
  209. package/dist/pages/AgentsLandingPage.js +4 -4
  210. package/dist/pages/AgentsLandingPage.js.map +1 -1
  211. package/dist/pages/AgentsPage.js +14 -14
  212. package/dist/pages/AgentsPage.js.map +1 -1
  213. package/dist/pages/AnalyticsPage.js +8 -8
  214. package/dist/pages/AnalyticsPage.js.map +1 -1
  215. package/dist/pages/CatalogDetailPage.js +11 -11
  216. package/dist/pages/CatalogDetailPage.js.map +1 -1
  217. package/dist/pages/CatalogPage.js +12 -9
  218. package/dist/pages/CatalogPage.js.map +1 -1
  219. package/dist/pages/ComponentDetailPage.js +7 -7
  220. package/dist/pages/ComponentDetailPage.js.map +1 -1
  221. package/dist/pages/ComponentsPage.js +12 -9
  222. package/dist/pages/ComponentsPage.js.map +1 -1
  223. package/dist/pages/EnvTopologyPage.js +2 -2
  224. package/dist/pages/EnvTopologyPage.js.map +1 -1
  225. package/dist/pages/GettingStartedPage.js +12 -12
  226. package/dist/pages/GettingStartedPage.js.map +1 -1
  227. package/dist/pages/HealthPage.js +12 -12
  228. package/dist/pages/HealthPage.js.map +1 -1
  229. package/dist/pages/LogsPage.js +4 -4
  230. package/dist/pages/LogsPage.js.map +1 -1
  231. package/dist/pages/NoteDetailsPage.js +5 -5
  232. package/dist/pages/NoteDetailsPage.js.map +1 -1
  233. package/dist/pages/OverviewPage.js +76 -43
  234. package/dist/pages/OverviewPage.js.map +1 -1
  235. package/dist/pages/PluginsCatalogPage.js +7 -4
  236. package/dist/pages/PluginsCatalogPage.js.map +1 -1
  237. package/dist/pages/ScheduledJobDetailPage.js +7 -7
  238. package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
  239. package/dist/pages/SessionDetailsPage.js +21 -21
  240. package/dist/pages/SessionDetailsPage.js.map +1 -1
  241. package/dist/pages/SessionsPage.js +20 -20
  242. package/dist/pages/SessionsPage.js.map +1 -1
  243. package/dist/pages/SettingsPage.js +64 -52
  244. package/dist/pages/SettingsPage.js.map +1 -1
  245. package/dist/pages/SharedSessionPage.js +21 -21
  246. package/dist/pages/SharedSessionPage.js.map +1 -1
  247. package/dist/pages/SharedVibeDeckPage.js +5 -5
  248. package/dist/pages/SharedVibeDeckPage.js.map +1 -1
  249. package/dist/pages/TargetDetailsPage.js +56 -65
  250. package/dist/pages/TargetDetailsPage.js.map +1 -1
  251. package/dist/pages/TargetsPage.js +4 -4
  252. package/dist/pages/TargetsPage.js.map +1 -1
  253. package/dist/pages/TemplateDetailPage.js +10 -10
  254. package/dist/pages/TemplateDetailPage.js.map +1 -1
  255. package/dist/pages/TemplatesPage.js +6 -5
  256. package/dist/pages/TemplatesPage.js.map +1 -1
  257. package/dist/pages/VibeAuditPage.js +3 -3
  258. package/dist/pages/VibeAuditPage.js.map +1 -1
  259. package/dist/pages/VibeCalendarPage.js +3 -3
  260. package/dist/pages/VibeCalendarPage.js.map +1 -1
  261. package/dist/pages/VibeDeckPage.js +3 -3
  262. package/dist/pages/VibeDeckPage.js.map +1 -1
  263. package/dist/pages/VibeDetailsPage.js +15 -15
  264. package/dist/pages/VibeDetailsPage.js.map +1 -1
  265. package/dist/pages/VibeTaskDetailPage.js +5 -5
  266. package/dist/pages/VibeTaskDetailPage.js.map +1 -1
  267. package/dist/pages/VibesPage.js +18 -18
  268. package/dist/pages/VibesPage.js.map +1 -1
  269. package/dist/pages/WebhooksPage.js +11 -11
  270. package/dist/pages/WebhooksPage.js.map +1 -1
  271. package/dist/pages/ai/AIBookmarksPage.js +1 -1
  272. package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
  273. package/dist/pages/ai/AISessionDetailPage.js +6 -6
  274. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  275. package/dist/pages/tunnels/AuditTab.js +1 -1
  276. package/dist/pages/tunnels/AuditTab.js.map +1 -1
  277. package/dist/pages/tunnels/DomainsTab.js +1 -1
  278. package/dist/pages/tunnels/DomainsTab.js.map +1 -1
  279. package/dist/pages/tunnels/ProvidersTab.js +1 -1
  280. package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
  281. package/dist/pages/tunnels/SessionsTab.js +1 -1
  282. package/dist/pages/tunnels/SessionsTab.js.map +1 -1
  283. package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
  284. package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
  285. package/dist/pages/tunnels/UsageTab.js +1 -1
  286. package/dist/pages/tunnels/UsageTab.js.map +1 -1
  287. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
  288. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
  289. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
  290. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  291. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
  292. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
  293. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
  294. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
  295. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"EnvTopologyPage.js","names":[],"sources":["../../src/pages/EnvTopologyPage.tsx"],"sourcesContent":["import type { FC, FormEvent } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Network, Plus, ArrowRight, RefreshCw } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { PageHeader, Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { notify } from '@/lib/toast';\nimport {\n useGetEnvTopologyQuery,\n useRegisterDeploymentMutation,\n usePromoteDeploymentMutation,\n type GetEnvTopologyQuery,\n} from '@/generated/wspace-operations';\nimport { DeploymentEnvironment } from '@/generated/wspace-types';\n\nconst ENVIRONMENTS: DeploymentEnvironment[] = [\n DeploymentEnvironment.Development,\n DeploymentEnvironment.Staging,\n DeploymentEnvironment.Production,\n];\n\nconst ENV_LABEL: Record<string, string> = {\n DEVELOPMENT: 'Development',\n STAGING: 'Staging',\n PRODUCTION: 'Production',\n};\n\n/**\n * EnvTopologyPage — the environment-topology matrix: every component (rows) and its current deployed\n * version across Development / Staging / Production (columns). Drift is highlighted (a cell whose\n * version differs from Production is amber; matching Production is green). Supports recording a\n * deployment and promoting a version between environments (both audited server-side).\n */\nexport const EnvTopologyPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const { hasPermission } = usePermissions();\n const canEdit = hasPermission(VIBE_PERMISSIONS.VIBE_UPDATE);\n\n const { data, loading, error, refetch } = useGetEnvTopologyQuery({\n fetchPolicy: 'cache-and-network',\n });\n const [registerDeployment, { loading: registering }] = useRegisterDeploymentMutation();\n const [promoteDeployment, { loading: promoting }] = usePromoteDeploymentMutation();\n\n const rows = useMemo<GetEnvTopologyQuery['envTopology']>(() => data?.envTopology ?? [], [data]);\n\n const [modal, setModal] = useState<null | 'register' | 'promote'>(null);\n const [formComponentId, setFormComponentId] = useState('');\n const [formEnv, setFormEnv] = useState<DeploymentEnvironment>(DeploymentEnvironment.Staging);\n const [formFromEnv, setFormFromEnv] = useState<DeploymentEnvironment>(\n DeploymentEnvironment.Staging\n );\n const [formToEnv, setFormToEnv] = useState<DeploymentEnvironment>(\n DeploymentEnvironment.Production\n );\n const [formVersion, setFormVersion] = useState('');\n\n const openRegister = (componentId: string, environment: DeploymentEnvironment): void => {\n setFormComponentId(componentId);\n setFormEnv(environment);\n setFormVersion('');\n setModal('register');\n };\n\n const closeModal = (): void => {\n setModal(null);\n setFormComponentId('');\n setFormVersion('');\n };\n\n const handleRegister = async (e: FormEvent): Promise<void> => {\n e.preventDefault();\n if (!formComponentId || formVersion.trim().length === 0) return;\n try {\n await registerDeployment({\n variables: {\n componentId: formComponentId,\n environment: formEnv,\n version: formVersion.trim(),\n },\n });\n await refetch();\n notify.success(\n tr('envTopology.recorded', 'Deployment recorded'),\n `${formVersion.trim()} → ${ENV_LABEL[formEnv]}`\n );\n closeModal();\n } catch (err) {\n notify.error(\n tr('envTopology.recordFailed', 'Could not record deployment'),\n (err as Error).message\n );\n }\n };\n\n const handlePromote = async (e: FormEvent): Promise<void> => {\n e.preventDefault();\n if (!formComponentId || formFromEnv === formToEnv) return;\n try {\n await promoteDeployment({\n variables: {\n componentId: formComponentId,\n fromEnvironment: formFromEnv,\n toEnvironment: formToEnv,\n },\n });\n await refetch();\n notify.success(\n tr('envTopology.promoted', 'Version promoted'),\n `${ENV_LABEL[formFromEnv]} → ${ENV_LABEL[formToEnv]}`\n );\n closeModal();\n } catch (err) {\n notify.error(tr('envTopology.promoteFailed', 'Could not promote'), (err as Error).message);\n }\n };\n\n const componentOptions = rows.map((r) => ({ id: r.component.id, name: r.component.name }));\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title={tr('envTopology.title', 'Environment Topology')}\n description={tr(\n 'envTopology.subtitle',\n 'Current deployed version of every component across Development, Staging and Production.'\n )}\n actions={\n canEdit && rows.length > 0 ? (\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setFormComponentId(componentOptions[0]?.id ?? '');\n setModal('promote');\n }}\n className=\"flex items-center gap-1.5 px-3 py-2 text-sm rounded-md border border-border-default text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowRight className=\"size-4\" />\n {tr('envTopology.promote', 'Promote version')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setFormComponentId(componentOptions[0]?.id ?? '');\n setFormEnv(DeploymentEnvironment.Staging);\n setFormVersion('');\n setModal('register');\n }}\n className=\"flex items-center gap-1.5 px-3 py-2 text-sm rounded-md bg-action-primary-bg text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"size-4\" />\n {tr('envTopology.record', 'Record deployment')}\n </button>\n </div>\n ) : undefined\n }\n />\n\n {loading && rows.length === 0 ? (\n <LoadingState />\n ) : error ? (\n <ErrorState message={error.message} onRetry={() => void refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n icon={<Network className=\"size-10\" />}\n title={tr('envTopology.emptyTitle', 'No components yet')}\n description={tr(\n 'envTopology.emptyDesc',\n 'Register components in the catalog, then record their deployments to see version state across environments here.'\n )}\n />\n ) : (\n <Card>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-default text-left text-text-secondary\">\n <th className=\"px-4 py-3 font-medium\">\n {tr('envTopology.component', 'Component')}\n </th>\n {ENVIRONMENTS.map((env) => (\n <th key={env} className=\"px-4 py-3 font-medium\">\n {ENV_LABEL[env]}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => {\n const prod =\n row.environments.find((e) => e.environment === 'PRODUCTION')?.version ?? null;\n return (\n <tr\n key={row.component.id}\n className=\"border-b border-border-subtle last:border-0\"\n >\n <td className=\"px-4 py-3\">\n <div className=\"font-medium text-text-primary\">{row.component.name}</div>\n <div className=\"text-xs text-text-tertiary\">{row.component.type}</div>\n </td>\n {ENVIRONMENTS.map((env) => {\n const cell = row.environments.find((e) => e.environment === env);\n const version = cell?.version ?? null;\n let tone = 'text-text-tertiary';\n let dot = 'bg-border-strong';\n if (version) {\n if (prod && version === prod) {\n tone = 'text-text-primary';\n dot = 'bg-status-success-text';\n } else if (prod && version !== prod) {\n tone = 'text-text-primary';\n dot = 'bg-status-warning-text';\n } else {\n tone = 'text-text-primary';\n dot = 'bg-accent-blue';\n }\n }\n return (\n <td key={env} className=\"px-4 py-3\">\n {version ? (\n <span className={`inline-flex items-center gap-2 font-mono ${tone}`}>\n <span\n className={`inline-block size-2 rounded-full ${dot}`}\n aria-hidden\n />\n {version}\n </span>\n ) : canEdit ? (\n <button\n type=\"button\"\n onClick={() => openRegister(row.component.id, env)}\n className=\"text-text-tertiary hover:text-text-primary\"\n >\n — {tr('envTopology.add', 'add')}\n </button>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n )}\n </td>\n );\n })}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </Card>\n )}\n\n {modal === 'register' && (\n <ModalShell\n title={tr('envTopology.recordTitle', 'Record a deployment')}\n onClose={closeModal}\n >\n <form onSubmit={(e) => void handleRegister(e)} className=\"space-y-3\">\n <LabeledSelect\n label={tr('envTopology.component', 'Component')}\n value={formComponentId}\n onChange={setFormComponentId}\n options={componentOptions.map((c) => ({ value: c.id, label: c.name }))}\n />\n <LabeledSelect\n label={tr('envTopology.environment', 'Environment')}\n value={formEnv}\n onChange={(v) => setFormEnv(v as DeploymentEnvironment)}\n options={ENVIRONMENTS.map((e) => ({ value: e, label: ENV_LABEL[e] ?? e }))}\n />\n <label className=\"block\">\n <span className=\"text-sm text-text-secondary\">\n {tr('envTopology.version', 'Version')}\n </span>\n <input\n value={formVersion}\n onChange={(e) => setFormVersion(e.target.value)}\n placeholder=\"v1.2.3\"\n className=\"mt-1 w-full font-mono text-sm px-3 py-2 rounded-md border border-border-default bg-bg-surface text-text-primary\"\n />\n </label>\n <ModalFooter\n onCancel={closeModal}\n submitLabel={tr('envTopology.record', 'Record deployment')}\n disabled={registering || !formComponentId || formVersion.trim().length === 0}\n />\n </form>\n </ModalShell>\n )}\n\n {modal === 'promote' && (\n <ModalShell\n title={tr('envTopology.promoteTitle', 'Promote a version')}\n onClose={closeModal}\n >\n <form onSubmit={(e) => void handlePromote(e)} className=\"space-y-3\">\n <LabeledSelect\n label={tr('envTopology.component', 'Component')}\n value={formComponentId}\n onChange={setFormComponentId}\n options={componentOptions.map((c) => ({ value: c.id, label: c.name }))}\n />\n <div className=\"flex items-center gap-2\">\n <LabeledSelect\n label={tr('envTopology.from', 'From')}\n value={formFromEnv}\n onChange={(v) => setFormFromEnv(v as DeploymentEnvironment)}\n options={ENVIRONMENTS.map((e) => ({ value: e, label: ENV_LABEL[e] ?? e }))}\n />\n <ArrowRight className=\"size-4 mt-5 text-text-tertiary\" />\n <LabeledSelect\n label={tr('envTopology.to', 'To')}\n value={formToEnv}\n onChange={(v) => setFormToEnv(v as DeploymentEnvironment)}\n options={ENVIRONMENTS.map((e) => ({ value: e, label: ENV_LABEL[e] ?? e }))}\n />\n </div>\n <ModalFooter\n onCancel={closeModal}\n submitLabel={tr('envTopology.promote', 'Promote version')}\n disabled={promoting || !formComponentId || formFromEnv === formToEnv}\n />\n </form>\n </ModalShell>\n )}\n </div>\n );\n};\n\nconst ModalShell: FC<{ title: string; onClose: () => void; children: React.ReactNode }> = ({\n title,\n onClose,\n children,\n}) => (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4\"\n onClick={onClose}\n >\n <div\n className=\"w-full max-w-md rounded-lg border border-border-default bg-bg-surface p-5 shadow-lg\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"mb-4 text-lg font-semibold text-text-primary\">{title}</h3>\n {children}\n </div>\n </div>\n);\n\nconst LabeledSelect: FC<{\n label: string;\n value: string;\n onChange: (v: string) => void;\n options: Array<{ value: string; label: string }>;\n}> = ({ label, value, onChange, options }) => (\n <label className=\"block flex-1\">\n <span className=\"text-sm text-text-secondary\">{label}</span>\n <select\n value={value}\n onChange={(e) => onChange(e.target.value)}\n className=\"mt-1 w-full text-sm px-3 py-2 rounded-md border border-border-default bg-bg-surface text-text-primary\"\n >\n {options.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </label>\n);\n\nconst ModalFooter: FC<{ onCancel: () => void; submitLabel: string; disabled: boolean }> = ({\n onCancel,\n submitLabel,\n disabled,\n}) => (\n <div className=\"flex items-center justify-end gap-2 pt-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-3 py-2 text-sm rounded-md text-text-secondary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={disabled}\n className=\"flex items-center gap-1.5 px-3 py-2 text-sm rounded-md bg-action-primary-bg text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n <RefreshCw className=\"size-4\" />\n {submitLabel}\n </button>\n </div>\n);\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,IAAM,IAAwC;CAC5C,EAAsB;CACtB,EAAsB;CACtB,EAAsB;CACvB,EAEK,IAAoC;CACxC,aAAa;CACb,SAAS;CACT,YAAY;CACb,EAQY,UAA4B;CACvC,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAE1B,EAAE,qBAAkB,GAAgB,EACpC,IAAU,EAAc,EAAiB,YAAY,EAErD,EAAE,SAAM,YAAS,UAAO,eAAY,EAAuB,EAC/D,aAAa,qBACd,CAAC,EACI,CAAC,GAAoB,EAAE,SAAS,OAAiB,GAA+B,EAChF,CAAC,GAAmB,EAAE,SAAS,OAAe,GAA8B,EAE5E,IAAO,QAAkD,GAAM,eAAe,EAAE,EAAE,CAAC,EAAK,CAAC,EAEzF,CAAC,GAAO,KAAY,EAAwC,KAAK,EACjE,CAAC,GAAiB,KAAsB,EAAS,GAAG,EACpD,CAAC,GAAS,KAAc,EAAgC,EAAsB,QAAQ,EACtF,CAAC,GAAa,KAAkB,EACpC,EAAsB,QACvB,EACK,CAAC,GAAW,KAAgB,EAChC,EAAsB,WACvB,EACK,CAAC,GAAa,KAAkB,EAAS,GAAG,EAE5C,KAAgB,GAAqB,MAA6C;AAItF,EAHA,EAAmB,EAAY,EAC/B,EAAW,EAAY,EACvB,EAAe,GAAG,EAClB,EAAS,WAAW;IAGhB,UAAyB;AAG7B,EAFA,EAAS,KAAK,EACd,EAAmB,GAAG,EACtB,EAAe,GAAG;IAGd,IAAiB,OAAO,MAAgC;AAC5D,QAAE,gBAAgB,EACd,GAAC,KAAmB,EAAY,MAAM,CAAC,WAAW,GACtD,KAAI;AAaF,GAZA,MAAM,EAAmB,EACvB,WAAW;IACT,aAAa;IACb,aAAa;IACb,SAAS,EAAY,MAAM;IAC5B,EACF,CAAC,EACF,MAAM,GAAS,EACf,EAAO,QACL,EAAG,wBAAwB,sBAAsB,EACjD,GAAG,EAAY,MAAM,CAAC,KAAK,EAAU,KACtC,EACD,GAAY;WACL,GAAK;AACZ,KAAO,MACL,EAAG,4BAA4B,8BAA8B,EAC5D,EAAc,QAChB;;IAIC,KAAgB,OAAO,MAAgC;AAC3D,QAAE,gBAAgB,EACd,GAAC,KAAmB,MAAgB,GACxC,KAAI;AAaF,GAZA,MAAM,EAAkB,EACtB,WAAW;IACT,aAAa;IACb,iBAAiB;IACjB,eAAe;IAChB,EACF,CAAC,EACF,MAAM,GAAS,EACf,EAAO,QACL,EAAG,wBAAwB,mBAAmB,EAC9C,GAAG,EAAU,GAAa,KAAK,EAAU,KAC1C,EACD,GAAY;WACL,GAAK;AACZ,KAAO,MAAM,EAAG,6BAA6B,oBAAoB,EAAG,EAAc,QAAQ;;IAIxF,IAAmB,EAAK,KAAK,OAAO;EAAE,IAAI,EAAE,UAAU;EAAI,MAAM,EAAE,UAAU;EAAM,EAAE;AAE1F,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,OAAO,EAAG,qBAAqB,uBAAuB;IACtD,aAAa,EACX,wBACA,0FACD;IACD,SACE,KAAW,EAAK,SAAS,IACvB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe;AAEb,OADA,EAAmB,EAAiB,IAAI,MAAM,GAAG,EACjD,EAAS,UAAU;;MAErB,WAAU;gBANZ,CAQE,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA,EAChC,EAAG,uBAAuB,kBAAkB,CACtC;SACT,kBAAC,UAAD;MACE,MAAK;MACL,eAAe;AAIb,OAHA,EAAmB,EAAiB,IAAI,MAAM,GAAG,EACjD,EAAW,EAAsB,QAAQ,EACzC,EAAe,GAAG,EAClB,EAAS,WAAW;;MAEtB,WAAU;gBARZ,CAUE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,sBAAsB,oBAAoB,CACvC;QACL;SACJ,KAAA;IAEN,CAAA;GAED,KAAW,EAAK,WAAW,IAC1B,kBAAC,GAAD,EAAgB,CAAA,GACd,IACF,kBAAC,GAAD;IAAY,SAAS,EAAM;IAAS,eAAe,KAAK,GAAS;IAAI,CAAA,GACnE,EAAK,WAAW,IAClB,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;IACrC,OAAO,EAAG,0BAA0B,oBAAoB;IACxD,aAAa,EACX,yBACA,mHACD;IACD,CAAA,GAEF,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;MAAI,WAAU;gBAAd,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,yBAAyB,YAAY;OACtC,CAAA,EACJ,EAAa,KAAK,MACjB,kBAAC,MAAD;OAAc,WAAU;iBACrB,EAAU;OACR,EAFI,EAEJ,CACL,CACC;SACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAK,KAAK,MAAQ;MACjB,IAAM,IACJ,EAAI,aAAa,MAAM,MAAM,EAAE,gBAAgB,aAAa,EAAE,WAAW;AAC3E,aACE,kBAAC,MAAD;OAEE,WAAU;iBAFZ,CAIE,kBAAC,MAAD;QAAI,WAAU;kBAAd,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAiC,EAAI,UAAU;SAAW,CAAA,EACzE,kBAAC,OAAD;SAAK,WAAU;mBAA8B,EAAI,UAAU;SAAW,CAAA,CACnE;WACJ,EAAa,KAAK,MAAQ;QAEzB,IAAM,IADO,EAAI,aAAa,MAAM,MAAM,EAAE,gBAAgB,EAAI,EAC1C,WAAW,MAC7B,IAAO,sBACP,IAAM;AAaV,eAZI,MACE,KAAQ,MAAY,KACtB,IAAO,qBACP,IAAM,4BACG,KAAQ,MAAY,KAC7B,IAAO,qBACP,IAAM,6BAEN,IAAO,qBACP,IAAM,oBAIR,kBAAC,MAAD;SAAc,WAAU;mBACrB,IACC,kBAAC,QAAD;UAAM,WAAW,4CAA4C;oBAA7D,CACE,kBAAC,QAAD;WACE,WAAW,oCAAoC;WAC/C,eAAA;WACA,CAAA,EACD,EACI;cACL,IACF,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAa,EAAI,UAAU,IAAI,EAAI;UAClD,WAAU;oBAHZ,CAIC,MACI,EAAG,mBAAmB,MAAM,CACxB;cAET,kBAAC,QAAD;UAAM,WAAU;oBAAqB;UAAQ,CAAA;SAE5C,EApBI,EAoBJ;SAEP,CACC;SAhDE,EAAI,UAAU,GAgDhB;OAEP,EACI,CAAA,CACF;;IACJ,CAAA,EACD,CAAA;GAGR,MAAU,cACT,kBAAC,GAAD;IACE,OAAO,EAAG,2BAA2B,sBAAsB;IAC3D,SAAS;cAET,kBAAC,QAAD;KAAM,WAAW,MAAM,KAAK,EAAe,EAAE;KAAE,WAAU;eAAzD;MACE,kBAAC,GAAD;OACE,OAAO,EAAG,yBAAyB,YAAY;OAC/C,OAAO;OACP,UAAU;OACV,SAAS,EAAiB,KAAK,OAAO;QAAE,OAAO,EAAE;QAAI,OAAO,EAAE;QAAM,EAAE;OACtE,CAAA;MACF,kBAAC,GAAD;OACE,OAAO,EAAG,2BAA2B,cAAc;OACnD,OAAO;OACP,WAAW,MAAM,EAAW,EAA2B;OACvD,SAAS,EAAa,KAAK,OAAO;QAAE,OAAO;QAAG,OAAO,EAAU,MAAM;QAAG,EAAE;OAC1E,CAAA;MACF,kBAAC,SAAD;OAAO,WAAU;iBAAjB,CACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAG,uBAAuB,UAAU;QAChC,CAAA,EACP,kBAAC,SAAD;QACE,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,aAAY;QACZ,WAAU;QACV,CAAA,CACI;;MACR,kBAAC,GAAD;OACE,UAAU;OACV,aAAa,EAAG,sBAAsB,oBAAoB;OAC1D,UAAU,KAAe,CAAC,KAAmB,EAAY,MAAM,CAAC,WAAW;OAC3E,CAAA;MACG;;IACI,CAAA;GAGd,MAAU,aACT,kBAAC,GAAD;IACE,OAAO,EAAG,4BAA4B,oBAAoB;IAC1D,SAAS;cAET,kBAAC,QAAD;KAAM,WAAW,MAAM,KAAK,GAAc,EAAE;KAAE,WAAU;eAAxD;MACE,kBAAC,GAAD;OACE,OAAO,EAAG,yBAAyB,YAAY;OAC/C,OAAO;OACP,UAAU;OACV,SAAS,EAAiB,KAAK,OAAO;QAAE,OAAO,EAAE;QAAI,OAAO,EAAE;QAAM,EAAE;OACtE,CAAA;MACF,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SACE,OAAO,EAAG,oBAAoB,OAAO;SACrC,OAAO;SACP,WAAW,MAAM,EAAe,EAA2B;SAC3D,SAAS,EAAa,KAAK,OAAO;UAAE,OAAO;UAAG,OAAO,EAAU,MAAM;UAAG,EAAE;SAC1E,CAAA;QACF,kBAAC,GAAD,EAAY,WAAU,kCAAmC,CAAA;QACzD,kBAAC,GAAD;SACE,OAAO,EAAG,kBAAkB,KAAK;SACjC,OAAO;SACP,WAAW,MAAM,EAAa,EAA2B;SACzD,SAAS,EAAa,KAAK,OAAO;UAAE,OAAO;UAAG,OAAO,EAAU,MAAM;UAAG,EAAE;SAC1E,CAAA;QACE;;MACN,kBAAC,GAAD;OACE,UAAU;OACV,aAAa,EAAG,uBAAuB,kBAAkB;OACzD,UAAU,KAAa,CAAC,KAAmB,MAAgB;OAC3D,CAAA;MACG;;IACI,CAAA;GAEX;;GAIJ,KAAqF,EACzF,UACA,YACA,kBAEA,kBAAC,OAAD;CACE,WAAU;CACV,SAAS;WAET,kBAAC,OAAD;EACE,WAAU;EACV,UAAU,MAAM,EAAE,iBAAiB;YAFrC,CAIE,kBAAC,MAAD;GAAI,WAAU;aAAgD;GAAW,CAAA,EACxE,EACG;;CACF,CAAA,EAGF,KAKA,EAAE,UAAO,UAAO,aAAU,iBAC9B,kBAAC,SAAD;CAAO,WAAU;WAAjB,CACE,kBAAC,QAAD;EAAM,WAAU;YAA+B;EAAa,CAAA,EAC5D,kBAAC,UAAD;EACS;EACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;EACzC,WAAU;YAET,EAAQ,KAAK,MACZ,kBAAC,UAAD;GAAsB,OAAO,EAAE;aAC5B,EAAE;GACI,EAFI,EAAE,MAEN,CACT;EACK,CAAA,CACH;IAGJ,KAAqF,EACzF,aACA,gBACA,kBAEA,kBAAC,OAAD;CAAK,WAAU;WAAf,CACE,kBAAC,UAAD;EACE,MAAK;EACL,SAAS;EACT,WAAU;YACX;EAEQ,CAAA,EACT,kBAAC,UAAD;EACE,MAAK;EACK;EACV,WAAU;YAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EACM;IACL"}
1
+ {"version":3,"file":"EnvTopologyPage.js","names":[],"sources":["../../src/pages/EnvTopologyPage.tsx"],"sourcesContent":["import type { FC, FormEvent } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Network, Plus, ArrowRight, RefreshCw } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { PageHeader, Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { notify } from '@/lib/toast';\nimport {\n useGetEnvTopologyQuery,\n useRegisterDeploymentMutation,\n usePromoteDeploymentMutation,\n type GetEnvTopologyQuery,\n} from '@/generated/wspace-operations';\nimport { DeploymentEnvironment } from '@/generated/wspace-types';\n\nconst ENVIRONMENTS: DeploymentEnvironment[] = [\n DeploymentEnvironment.Development,\n DeploymentEnvironment.Staging,\n DeploymentEnvironment.Production,\n];\n\nconst ENV_LABEL: Record<string, string> = {\n DEVELOPMENT: 'Development',\n STAGING: 'Staging',\n PRODUCTION: 'Production',\n};\n\n/**\n * EnvTopologyPage — the environment-topology matrix: every component (rows) and its current deployed\n * version across Development / Staging / Production (columns). Drift is highlighted (a cell whose\n * version differs from Production is amber; matching Production is green). Supports recording a\n * deployment and promoting a version between environments (both audited server-side).\n */\nexport const EnvTopologyPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const { hasPermission } = usePermissions();\n const canEdit = hasPermission(VIBE_PERMISSIONS.VIBE_UPDATE);\n\n const { data, loading, error, refetch } = useGetEnvTopologyQuery({\n fetchPolicy: 'cache-and-network',\n });\n const [registerDeployment, { loading: registering }] = useRegisterDeploymentMutation();\n const [promoteDeployment, { loading: promoting }] = usePromoteDeploymentMutation();\n\n const rows = useMemo<GetEnvTopologyQuery['envTopology']>(() => data?.envTopology ?? [], [data]);\n\n const [modal, setModal] = useState<null | 'register' | 'promote'>(null);\n const [formComponentId, setFormComponentId] = useState('');\n const [formEnv, setFormEnv] = useState<DeploymentEnvironment>(DeploymentEnvironment.Staging);\n const [formFromEnv, setFormFromEnv] = useState<DeploymentEnvironment>(\n DeploymentEnvironment.Staging\n );\n const [formToEnv, setFormToEnv] = useState<DeploymentEnvironment>(\n DeploymentEnvironment.Production\n );\n const [formVersion, setFormVersion] = useState('');\n\n const openRegister = (componentId: string, environment: DeploymentEnvironment): void => {\n setFormComponentId(componentId);\n setFormEnv(environment);\n setFormVersion('');\n setModal('register');\n };\n\n const closeModal = (): void => {\n setModal(null);\n setFormComponentId('');\n setFormVersion('');\n };\n\n const handleRegister = async (e: FormEvent): Promise<void> => {\n e.preventDefault();\n if (!formComponentId || formVersion.trim().length === 0) return;\n try {\n await registerDeployment({\n variables: {\n componentId: formComponentId,\n environment: formEnv,\n version: formVersion.trim(),\n },\n });\n await refetch();\n notify.success(\n tr('envTopology.recorded', 'Deployment recorded'),\n `${formVersion.trim()} → ${ENV_LABEL[formEnv]}`\n );\n closeModal();\n } catch (err) {\n notify.error(\n tr('envTopology.recordFailed', 'Could not record deployment'),\n (err as Error).message\n );\n }\n };\n\n const handlePromote = async (e: FormEvent): Promise<void> => {\n e.preventDefault();\n if (!formComponentId || formFromEnv === formToEnv) return;\n try {\n await promoteDeployment({\n variables: {\n componentId: formComponentId,\n fromEnvironment: formFromEnv,\n toEnvironment: formToEnv,\n },\n });\n await refetch();\n notify.success(\n tr('envTopology.promoted', 'Version promoted'),\n `${ENV_LABEL[formFromEnv]} → ${ENV_LABEL[formToEnv]}`\n );\n closeModal();\n } catch (err) {\n notify.error(tr('envTopology.promoteFailed', 'Could not promote'), (err as Error).message);\n }\n };\n\n const componentOptions = rows.map((r) => ({ id: r.component.id, name: r.component.name }));\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title={tr('envTopology.title', 'Environment Topology')}\n description={tr(\n 'envTopology.subtitle',\n 'Current deployed version of every component across Development, Staging and Production.'\n )}\n actions={\n canEdit && rows.length > 0 ? (\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setFormComponentId(componentOptions[0]?.id ?? '');\n setModal('promote');\n }}\n className=\"flex items-center gap-1.5 px-3 py-2 text-sm rounded-md border border-border-default text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowRight className=\"size-4\" />\n {tr('envTopology.promote', 'Promote version')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setFormComponentId(componentOptions[0]?.id ?? '');\n setFormEnv(DeploymentEnvironment.Staging);\n setFormVersion('');\n setModal('register');\n }}\n className=\"flex items-center gap-1.5 px-3 py-2 text-sm rounded-md bg-action-primary-bg text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"size-4\" />\n {tr('envTopology.record', 'Record deployment')}\n </button>\n </div>\n ) : undefined\n }\n />\n\n {loading && rows.length === 0 ? (\n <LoadingState />\n ) : error ? (\n <ErrorState message={error.message} onRetry={() => void refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n icon={<Network className=\"size-10\" />}\n title={tr('envTopology.emptyTitle', 'No components yet')}\n description={tr(\n 'envTopology.emptyDesc',\n 'Register components in the catalog, then record their deployments to see version state across environments here.'\n )}\n />\n ) : (\n <Card>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-default text-left text-text-secondary\">\n <th className=\"px-4 py-3 font-medium\">\n {tr('envTopology.component', 'Component')}\n </th>\n {ENVIRONMENTS.map((env) => (\n <th key={env} className=\"px-4 py-3 font-medium\">\n {ENV_LABEL[env]}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => {\n const prod =\n row.environments.find((e) => e.environment === 'PRODUCTION')?.version ?? null;\n return (\n <tr\n key={row.component.id}\n className=\"border-b border-border-subtle last:border-0\"\n >\n <td className=\"px-4 py-3\">\n <div className=\"font-medium text-text-primary\">{row.component.name}</div>\n <div className=\"text-xs text-text-tertiary\">{row.component.type}</div>\n </td>\n {ENVIRONMENTS.map((env) => {\n const cell = row.environments.find((e) => e.environment === env);\n const version = cell?.version ?? null;\n let tone = 'text-text-tertiary';\n let dot = 'bg-border-strong';\n if (version) {\n if (prod && version === prod) {\n tone = 'text-text-primary';\n dot = 'bg-status-success-text';\n } else if (prod && version !== prod) {\n tone = 'text-text-primary';\n dot = 'bg-status-warning-text';\n } else {\n tone = 'text-text-primary';\n dot = 'bg-accent-blue';\n }\n }\n return (\n <td key={env} className=\"px-4 py-3\">\n {version ? (\n <span className={`inline-flex items-center gap-2 font-mono ${tone}`}>\n <span\n className={`inline-block size-2 rounded-full ${dot}`}\n aria-hidden\n />\n {version}\n </span>\n ) : canEdit ? (\n <button\n type=\"button\"\n onClick={() => openRegister(row.component.id, env)}\n className=\"text-text-tertiary hover:text-text-primary\"\n >\n — {tr('envTopology.add', 'add')}\n </button>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n )}\n </td>\n );\n })}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </Card>\n )}\n\n {modal === 'register' && (\n <ModalShell\n title={tr('envTopology.recordTitle', 'Record a deployment')}\n onClose={closeModal}\n >\n <form onSubmit={(e) => void handleRegister(e)} className=\"space-y-3\">\n <LabeledSelect\n label={tr('envTopology.component', 'Component')}\n value={formComponentId}\n onChange={setFormComponentId}\n options={componentOptions.map((c) => ({ value: c.id, label: c.name }))}\n />\n <LabeledSelect\n label={tr('envTopology.environment', 'Environment')}\n value={formEnv}\n onChange={(v) => setFormEnv(v as DeploymentEnvironment)}\n options={ENVIRONMENTS.map((e) => ({ value: e, label: ENV_LABEL[e] ?? e }))}\n />\n <label className=\"block\">\n <span className=\"text-sm text-text-secondary\">\n {tr('envTopology.version', 'Version')}\n </span>\n <input\n value={formVersion}\n onChange={(e) => setFormVersion(e.target.value)}\n placeholder=\"v1.2.3\"\n className=\"mt-1 w-full font-mono text-sm px-3 py-2 rounded-md border border-border-default bg-bg-surface text-text-primary\"\n />\n </label>\n <ModalFooter\n onCancel={closeModal}\n submitLabel={tr('envTopology.record', 'Record deployment')}\n disabled={registering || !formComponentId || formVersion.trim().length === 0}\n />\n </form>\n </ModalShell>\n )}\n\n {modal === 'promote' && (\n <ModalShell\n title={tr('envTopology.promoteTitle', 'Promote a version')}\n onClose={closeModal}\n >\n <form onSubmit={(e) => void handlePromote(e)} className=\"space-y-3\">\n <LabeledSelect\n label={tr('envTopology.component', 'Component')}\n value={formComponentId}\n onChange={setFormComponentId}\n options={componentOptions.map((c) => ({ value: c.id, label: c.name }))}\n />\n <div className=\"flex items-center gap-2\">\n <LabeledSelect\n label={tr('envTopology.from', 'From')}\n value={formFromEnv}\n onChange={(v) => setFormFromEnv(v as DeploymentEnvironment)}\n options={ENVIRONMENTS.map((e) => ({ value: e, label: ENV_LABEL[e] ?? e }))}\n />\n <ArrowRight className=\"size-4 mt-5 text-text-tertiary\" />\n <LabeledSelect\n label={tr('envTopology.to', 'To')}\n value={formToEnv}\n onChange={(v) => setFormToEnv(v as DeploymentEnvironment)}\n options={ENVIRONMENTS.map((e) => ({ value: e, label: ENV_LABEL[e] ?? e }))}\n />\n </div>\n <ModalFooter\n onCancel={closeModal}\n submitLabel={tr('envTopology.promote', 'Promote version')}\n disabled={promoting || !formComponentId || formFromEnv === formToEnv}\n />\n </form>\n </ModalShell>\n )}\n </div>\n );\n};\n\nconst ModalShell: FC<{ title: string; onClose: () => void; children: React.ReactNode }> = ({\n title,\n onClose,\n children,\n}) => (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={onClose}\n >\n <div\n className=\"w-full max-w-md rounded-card border border-border-subtle bg-bg-elevated p-6 shadow-[var(--shadow-elevation-4)]\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"mb-4 text-lg font-semibold text-text-primary\">{title}</h3>\n {children}\n </div>\n </div>\n);\n\nconst LabeledSelect: FC<{\n label: string;\n value: string;\n onChange: (v: string) => void;\n options: Array<{ value: string; label: string }>;\n}> = ({ label, value, onChange, options }) => (\n <label className=\"block flex-1\">\n <span className=\"text-sm text-text-secondary\">{label}</span>\n <select\n value={value}\n onChange={(e) => onChange(e.target.value)}\n className=\"mt-1 w-full text-sm px-3 py-2 rounded-md border border-border-default bg-bg-surface text-text-primary\"\n >\n {options.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </label>\n);\n\nconst ModalFooter: FC<{ onCancel: () => void; submitLabel: string; disabled: boolean }> = ({\n onCancel,\n submitLabel,\n disabled,\n}) => (\n <div className=\"flex items-center justify-end gap-2 pt-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-3 py-2 text-sm rounded-md text-text-secondary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={disabled}\n className=\"flex items-center gap-1.5 px-3 py-2 text-sm rounded-md bg-action-primary-bg text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n <RefreshCw className=\"size-4\" />\n {submitLabel}\n </button>\n </div>\n);\n"],"mappings":"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"}
@@ -26,14 +26,14 @@ function y() {
26
26
  e === C[0].id ? t.delete("tab") : t.set("tab", e), S(t, { replace: !0 });
27
27
  }, [E, D] = a("");
28
28
  return /* @__PURE__ */ p(_, {
29
- className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
29
+ className: "space-y-8 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
30
30
  children: [
31
31
  /* @__PURE__ */ f(e, {
32
32
  title: b("gettingStarted.title", "Getting Started"),
33
33
  description: b("gettingStarted.description", "Install and configure the VibeControls Agent CLI")
34
34
  }),
35
35
  /* @__PURE__ */ f("div", {
36
- className: "border-b border-border-default",
36
+ className: "border-b border-border-subtle",
37
37
  children: /* @__PURE__ */ f(m, {
38
38
  items: C,
39
39
  activeTabId: w,
@@ -45,11 +45,11 @@ function y() {
45
45
  className: "space-y-6",
46
46
  children: [
47
47
  /* @__PURE__ */ f("div", {
48
- className: "relative border border-accent-purple/20 bg-accent-purple-subtle rounded-xl p-6 sm:p-8",
48
+ className: "relative border border-border-subtle bg-accent-purple-subtle rounded-card p-6 sm:p-8 shadow-elevation-1",
49
49
  children: /* @__PURE__ */ p("div", {
50
50
  className: "flex items-start gap-4",
51
51
  children: [/* @__PURE__ */ f("div", {
52
- className: "flex-shrink-0 size-12 rounded-xl bg-accent-purple/10 flex items-center justify-center",
52
+ className: "flex-shrink-0 size-12 rounded-xl bg-[var(--color-accent-soft)] flex items-center justify-center",
53
53
  children: /* @__PURE__ */ f(s, { className: "size-6 text-accent-purple" })
54
54
  }), /* @__PURE__ */ p("div", {
55
55
  className: "flex-1",
@@ -68,7 +68,7 @@ function y() {
68
68
  children: /* @__PURE__ */ p("div", {
69
69
  className: "p-5 sm:p-6",
70
70
  children: [/* @__PURE__ */ f(n, { pinnedAgentVersion: E || void 0 }), /* @__PURE__ */ p("div", {
71
- className: "mt-6 pt-4 border-t border-border-default",
71
+ className: "mt-6 pt-4 border-t border-border-subtle",
72
72
  children: [/* @__PURE__ */ p("label", {
73
73
  htmlFor: "pinned-version",
74
74
  className: "text-xs font-medium text-text-secondary flex items-center gap-1.5 mb-1.5",
@@ -84,7 +84,7 @@ function y() {
84
84
  placeholder: b("gettingStarted.pinVersionPlaceholder", "e.g. 2026.408.1 (leave blank for latest)"),
85
85
  value: E,
86
86
  onChange: (e) => D(e.target.value),
87
- className: "w-full max-w-xs px-3 py-2 rounded-md border border-border-default bg-bg-elevated text-sm"
87
+ className: "w-full max-w-xs px-3 py-2 rounded-input border border-border-default bg-bg-elevated text-sm text-text-primary focus-visible:outline-none focus-visible:border-border-strong focus-visible:shadow-[var(--shadow-focus)]"
88
88
  })]
89
89
  })]
90
90
  })
@@ -103,7 +103,7 @@ function y() {
103
103
  b("gettingStarted.nextStep1Prefix", "Start the agent:"),
104
104
  " ",
105
105
  /* @__PURE__ */ f("code", {
106
- className: "bg-bg-sunken px-1 py-0.5 rounded",
106
+ className: "font-mono text-xs bg-bg-sunken/60 text-text-secondary px-1.5 py-0.5 rounded",
107
107
  children: "vibecontrols auth login"
108
108
  }),
109
109
  " ",
@@ -113,14 +113,14 @@ function y() {
113
113
  b("gettingStarted.nextStep2Prefix", "Select a workspace and inspect agents:"),
114
114
  " ",
115
115
  /* @__PURE__ */ f("code", {
116
- className: "bg-bg-sunken px-1 py-0.5 rounded",
116
+ className: "font-mono text-xs bg-bg-sunken/60 text-text-secondary px-1.5 py-0.5 rounded",
117
117
  children: "vibecontrols workspace set <id>"
118
118
  }),
119
119
  " ",
120
120
  b("gettingStarted.nextStep2And", "then"),
121
121
  " ",
122
122
  /* @__PURE__ */ f("code", {
123
- className: "bg-bg-sunken px-1 py-0.5 rounded",
123
+ className: "font-mono text-xs bg-bg-sunken/60 text-text-secondary px-1.5 py-0.5 rounded",
124
124
  children: "vibecontrols agents list"
125
125
  }),
126
126
  "."
@@ -208,7 +208,7 @@ function y() {
208
208
  desc: b("gettingStarted.cmdAuthLogout", "Sign out of the CLI")
209
209
  }
210
210
  ].map((e) => /* @__PURE__ */ p(h, {
211
- className: "flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3 px-3 py-1.5 rounded hover:bg-bg-sunken transition-colors",
211
+ className: "flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3 px-3 py-2 rounded-button hover:bg-bg-sunken transition-colors",
212
212
  children: [/* @__PURE__ */ p("code", {
213
213
  className: "text-xs font-mono text-text-primary sm:w-72 flex-shrink-0",
214
214
  children: ["$ ", e.cmd]
@@ -219,12 +219,12 @@ function y() {
219
219
  }, e.cmd))
220
220
  }),
221
221
  /* @__PURE__ */ p("div", {
222
- className: "mt-4 pt-4 border-t border-border-default flex flex-wrap items-center gap-3",
222
+ className: "mt-4 pt-4 border-t border-border-subtle flex flex-wrap items-center gap-3",
223
223
  children: [/* @__PURE__ */ p("a", {
224
224
  href: v,
225
225
  target: "_blank",
226
226
  rel: "noopener noreferrer",
227
- className: "inline-flex items-center gap-2 px-3 py-1.5 text-xs border border-border-default text-text-secondary rounded-md hover:bg-bg-sunken transition-colors",
227
+ className: "inline-flex items-center gap-2 px-3 py-1.5 text-xs border border-border-default text-text-secondary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-colors",
228
228
  children: [
229
229
  b("gettingStarted.cliNpmPackage", "CLI npm package"),
230
230
  " ",
@@ -1 +1 @@
1
- {"version":3,"file":"GettingStartedPage.js","names":[],"sources":["../../src/pages/GettingStartedPage.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Terminal, ExternalLink, Download, Info } from 'lucide-react';\nimport { PageHeader, Card, InstallTabs } from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nconst CLI_NPM_URL = 'https://www.npmjs.com/package/@vibecontrols/cli';\n\n/**\n * Getting Started page — a tabbed guide to installing and using the\n * VibeControls Agent CLI. Two top-level tabs:\n *\n * • Install — shows curl / npm commands for Linux, macOS, Windows via\n * the shared `InstallTabs` component.\n * • CLI Reference — a compact reference of the most common commands.\n *\n * Bun is the only machine prerequisite — the curl install script installs\n * it automatically at a pinned version (v1.3.10). OAuth2 is always enabled\n * in the gateway so no manual configuration is needed.\n */\nexport function GettingStartedPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n const [params, setParams] = useSearchParams();\n const tabs: TabItem[] = useMemo(\n () => [\n { id: 'install', label: tr('gettingStarted.installTab', 'Install') },\n { id: 'cli', label: tr('gettingStarted.cliTab', 'CLI Reference') },\n ],\n [tr]\n );\n const activeId = params.get('tab') ?? tabs[0].id;\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) next.delete('tab');\n else next.set('tab', id);\n setParams(next, { replace: true });\n };\n\n const [pinnedVersion, setPinnedVersion] = useState('');\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={tr('gettingStarted.title', 'Getting Started')}\n description={tr(\n 'gettingStarted.description',\n 'Install and configure the VibeControls Agent CLI'\n )}\n />\n\n <div className=\"border-b border-border-default\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n\n {activeId === 'install' && (\n <div className=\"space-y-6\">\n {/* Hero */}\n <div className=\"relative border border-accent-purple/20 bg-accent-purple-subtle rounded-xl p-6 sm:p-8\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex-shrink-0 size-12 rounded-xl bg-accent-purple/10 flex items-center justify-center\">\n <Download className=\"size-6 text-accent-purple\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-lg sm:text-xl font-semibold text-text-primary mb-1\">\n {tr('gettingStarted.installAgent', 'Install the Agent')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'gettingStarted.installAgentDesc',\n 'Run a single command on the machine you want to manage. The recommended curl script installs bun (pinned) and the agent automatically — no other prerequisites.'\n )}\n </p>\n </div>\n </div>\n </div>\n\n {/* Install tabs */}\n <Card padding=\"none\">\n <div className=\"p-5 sm:p-6\">\n <InstallTabs pinnedAgentVersion={pinnedVersion || undefined} />\n\n <div className=\"mt-6 pt-4 border-t border-border-default\">\n <label\n htmlFor=\"pinned-version\"\n className=\"text-xs font-medium text-text-secondary flex items-center gap-1.5 mb-1.5\"\n >\n <Info className=\"size-3.5\" />{' '}\n {tr('gettingStarted.pinVersion', 'Pin a specific agent version (optional)')}\n </label>\n <input\n aria-label=\"pinned-version\"\n id=\"pinned-version\"\n type=\"text\"\n placeholder={tr(\n 'gettingStarted.pinVersionPlaceholder',\n 'e.g. 2026.408.1 (leave blank for latest)'\n )}\n value={pinnedVersion}\n onChange={(e) => setPinnedVersion(e.target.value)}\n className=\"w-full max-w-xs px-3 py-2 rounded-md border border-border-default bg-bg-elevated text-sm\"\n />\n </div>\n </div>\n </Card>\n\n {/* Next steps */}\n <Card padding=\"none\">\n <div className=\"p-5 sm:p-6\">\n <h3 className=\"text-base font-semibold text-text-primary mb-3\">\n {tr('gettingStarted.nextSteps', 'Next steps')}\n </h3>\n <ol className=\"list-decimal list-inside space-y-2 text-sm text-text-secondary\">\n <li>\n {tr('gettingStarted.nextStep1Prefix', 'Start the agent:')}{' '}\n <code className=\"bg-bg-sunken px-1 py-0.5 rounded\">vibecontrols auth login</code>{' '}\n {tr(\n 'gettingStarted.nextStep1Suffix',\n 'to authenticate the CLI before running workspace commands.'\n )}\n </li>\n <li>\n {tr('gettingStarted.nextStep2Prefix', 'Select a workspace and inspect agents:')}{' '}\n <code className=\"bg-bg-sunken px-1 py-0.5 rounded\">\n vibecontrols workspace set &lt;id&gt;\n </code>{' '}\n {tr('gettingStarted.nextStep2And', 'then')}{' '}\n <code className=\"bg-bg-sunken px-1 py-0.5 rounded\">vibecontrols agents list</code>\n .\n </li>\n <li>\n {tr('gettingStarted.nextStep3Prefix', 'In this UI, go to')}{' '}\n <strong>\n {tr('gettingStarted.targetsAndAgents', 'Targets & Agents')} →{' '}\n {tr('gettingStarted.addAgent', 'Add Agent')}\n </strong>\n {tr(\n 'gettingStarted.nextStep3Suffix',\n ', pick or create a target, and paste the URL and key from step 2.'\n )}\n </li>\n </ol>\n </div>\n </Card>\n </div>\n )}\n\n {activeId === 'cli' && (\n <Card padding=\"none\">\n <div className=\"p-5 sm:p-6\">\n <h3 className=\"text-base font-semibold text-text-primary mb-4 flex items-center gap-2\">\n <Terminal className=\"size-5 text-accent-purple\" />\n {tr('gettingStarted.cliQuickRef', 'CLI Quick Reference')}\n </h3>\n <MotionList stagger={0.04} className=\"space-y-2\">\n {[\n {\n cmd: 'vibecontrols auth login',\n desc: tr('gettingStarted.cmdAuthLogin', 'Sign in with OAuth2 Device Code'),\n },\n {\n cmd: 'vibecontrols auth whoami',\n desc: tr('gettingStarted.cmdAuthWhoami', 'Show the active signed-in identity'),\n },\n {\n cmd: 'vibecontrols workspace set <id>',\n desc: tr('gettingStarted.cmdWorkspaceSet', 'Choose the workspace context'),\n },\n {\n cmd: 'vibecontrols vibes list',\n desc: tr('gettingStarted.cmdVibesList', 'List VibeControls vibes'),\n },\n {\n cmd: 'vibecontrols agents list',\n desc: tr('gettingStarted.cmdAgentsList', 'List registered agents'),\n },\n {\n cmd: 'vibecontrols sessions list',\n desc: tr('gettingStarted.cmdSessionsList', 'List workspace sessions'),\n },\n {\n cmd: 'vibecontrols tags create --key bug --label Bug',\n desc: tr('gettingStarted.cmdTagsCreate', 'Create a workspace tag'),\n },\n {\n cmd: 'vibecontrols rbac check <user> read project',\n desc: tr('gettingStarted.cmdRbacCheck', 'Check RBAC access'),\n },\n {\n cmd: 'vibecontrols products feature-flags list',\n desc: tr('gettingStarted.cmdFeatureFlagsList', 'List product feature flags'),\n },\n {\n cmd: 'vibecontrols devportal apps list --status PUBLISHED',\n desc: tr(\n 'gettingStarted.cmdDevportalApps',\n 'List published Developer Portal apps'\n ),\n },\n {\n cmd: 'vibecontrols billing subscriptions list',\n desc: tr('gettingStarted.cmdBillingSubscriptions', 'List billing subscriptions'),\n },\n {\n cmd: 'vibecontrols health check',\n desc: tr('gettingStarted.cmdHealthCheck', 'Run platform health checks'),\n },\n {\n cmd: 'vibecontrols auth logout',\n desc: tr('gettingStarted.cmdAuthLogout', 'Sign out of the CLI'),\n },\n ].map((item) => (\n <MotionItem\n key={item.cmd}\n className=\"flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3 px-3 py-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <code className=\"text-xs font-mono text-text-primary sm:w-72 flex-shrink-0\">\n $ {item.cmd}\n </code>\n <span className=\"text-xs text-text-muted\">{item.desc}</span>\n </MotionItem>\n ))}\n </MotionList>\n <div className=\"mt-4 pt-4 border-t border-border-default flex flex-wrap items-center gap-3\">\n <a\n href={CLI_NPM_URL}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-xs border border-border-default text-text-secondary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n {tr('gettingStarted.cliNpmPackage', 'CLI npm package')}{' '}\n <ExternalLink className=\"size-3\" />\n </a>\n <span className=\"text-xs text-text-muted\">\n {tr('gettingStarted.cliNpmLatest', '@vibecontrols/cli (latest)')}\n </span>\n </div>\n </div>\n </Card>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"GettingStartedPage.js","names":[],"sources":["../../src/pages/GettingStartedPage.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Terminal, ExternalLink, Download, Info } from 'lucide-react';\nimport { PageHeader, Card, InstallTabs } from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nconst CLI_NPM_URL = 'https://www.npmjs.com/package/@vibecontrols/cli';\n\n/**\n * Getting Started page — a tabbed guide to installing and using the\n * VibeControls Agent CLI. Two top-level tabs:\n *\n * • Install — shows curl / npm commands for Linux, macOS, Windows via\n * the shared `InstallTabs` component.\n * • CLI Reference — a compact reference of the most common commands.\n *\n * Bun is the only machine prerequisite — the curl install script installs\n * it automatically at a pinned version (v1.3.10). OAuth2 is always enabled\n * in the gateway so no manual configuration is needed.\n */\nexport function GettingStartedPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n const [params, setParams] = useSearchParams();\n const tabs: TabItem[] = useMemo(\n () => [\n { id: 'install', label: tr('gettingStarted.installTab', 'Install') },\n { id: 'cli', label: tr('gettingStarted.cliTab', 'CLI Reference') },\n ],\n [tr]\n );\n const activeId = params.get('tab') ?? tabs[0].id;\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) next.delete('tab');\n else next.set('tab', id);\n setParams(next, { replace: true });\n };\n\n const [pinnedVersion, setPinnedVersion] = useState('');\n\n return (\n <MotionPage className=\"space-y-8 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <PageHeader\n title={tr('gettingStarted.title', 'Getting Started')}\n description={tr(\n 'gettingStarted.description',\n 'Install and configure the VibeControls Agent CLI'\n )}\n />\n\n <div className=\"border-b border-border-subtle\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n\n {activeId === 'install' && (\n <div className=\"space-y-6\">\n {/* Hero */}\n <div className=\"relative border border-border-subtle bg-accent-purple-subtle rounded-card p-6 sm:p-8 shadow-elevation-1\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex-shrink-0 size-12 rounded-xl bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Download className=\"size-6 text-accent-purple\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-lg sm:text-xl font-semibold text-text-primary mb-1\">\n {tr('gettingStarted.installAgent', 'Install the Agent')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'gettingStarted.installAgentDesc',\n 'Run a single command on the machine you want to manage. The recommended curl script installs bun (pinned) and the agent automatically — no other prerequisites.'\n )}\n </p>\n </div>\n </div>\n </div>\n\n {/* Install tabs */}\n <Card padding=\"none\">\n <div className=\"p-5 sm:p-6\">\n <InstallTabs pinnedAgentVersion={pinnedVersion || undefined} />\n\n <div className=\"mt-6 pt-4 border-t border-border-subtle\">\n <label\n htmlFor=\"pinned-version\"\n className=\"text-xs font-medium text-text-secondary flex items-center gap-1.5 mb-1.5\"\n >\n <Info className=\"size-3.5\" />{' '}\n {tr('gettingStarted.pinVersion', 'Pin a specific agent version (optional)')}\n </label>\n <input\n aria-label=\"pinned-version\"\n id=\"pinned-version\"\n type=\"text\"\n placeholder={tr(\n 'gettingStarted.pinVersionPlaceholder',\n 'e.g. 2026.408.1 (leave blank for latest)'\n )}\n value={pinnedVersion}\n onChange={(e) => setPinnedVersion(e.target.value)}\n className=\"w-full max-w-xs px-3 py-2 rounded-input border border-border-default bg-bg-elevated text-sm text-text-primary focus-visible:outline-none focus-visible:border-border-strong focus-visible:shadow-[var(--shadow-focus)]\"\n />\n </div>\n </div>\n </Card>\n\n {/* Next steps */}\n <Card padding=\"none\">\n <div className=\"p-5 sm:p-6\">\n <h3 className=\"text-base font-semibold text-text-primary mb-3\">\n {tr('gettingStarted.nextSteps', 'Next steps')}\n </h3>\n <ol className=\"list-decimal list-inside space-y-2 text-sm text-text-secondary\">\n <li>\n {tr('gettingStarted.nextStep1Prefix', 'Start the agent:')}{' '}\n <code className=\"font-mono text-xs bg-bg-sunken/60 text-text-secondary px-1.5 py-0.5 rounded\">\n vibecontrols auth login\n </code>{' '}\n {tr(\n 'gettingStarted.nextStep1Suffix',\n 'to authenticate the CLI before running workspace commands.'\n )}\n </li>\n <li>\n {tr('gettingStarted.nextStep2Prefix', 'Select a workspace and inspect agents:')}{' '}\n <code className=\"font-mono text-xs bg-bg-sunken/60 text-text-secondary px-1.5 py-0.5 rounded\">\n vibecontrols workspace set &lt;id&gt;\n </code>{' '}\n {tr('gettingStarted.nextStep2And', 'then')}{' '}\n <code className=\"font-mono text-xs bg-bg-sunken/60 text-text-secondary px-1.5 py-0.5 rounded\">\n vibecontrols agents list\n </code>\n .\n </li>\n <li>\n {tr('gettingStarted.nextStep3Prefix', 'In this UI, go to')}{' '}\n <strong>\n {tr('gettingStarted.targetsAndAgents', 'Targets & Agents')} →{' '}\n {tr('gettingStarted.addAgent', 'Add Agent')}\n </strong>\n {tr(\n 'gettingStarted.nextStep3Suffix',\n ', pick or create a target, and paste the URL and key from step 2.'\n )}\n </li>\n </ol>\n </div>\n </Card>\n </div>\n )}\n\n {activeId === 'cli' && (\n <Card padding=\"none\">\n <div className=\"p-5 sm:p-6\">\n <h3 className=\"text-base font-semibold text-text-primary mb-4 flex items-center gap-2\">\n <Terminal className=\"size-5 text-accent-purple\" />\n {tr('gettingStarted.cliQuickRef', 'CLI Quick Reference')}\n </h3>\n <MotionList stagger={0.04} className=\"space-y-2\">\n {[\n {\n cmd: 'vibecontrols auth login',\n desc: tr('gettingStarted.cmdAuthLogin', 'Sign in with OAuth2 Device Code'),\n },\n {\n cmd: 'vibecontrols auth whoami',\n desc: tr('gettingStarted.cmdAuthWhoami', 'Show the active signed-in identity'),\n },\n {\n cmd: 'vibecontrols workspace set <id>',\n desc: tr('gettingStarted.cmdWorkspaceSet', 'Choose the workspace context'),\n },\n {\n cmd: 'vibecontrols vibes list',\n desc: tr('gettingStarted.cmdVibesList', 'List VibeControls vibes'),\n },\n {\n cmd: 'vibecontrols agents list',\n desc: tr('gettingStarted.cmdAgentsList', 'List registered agents'),\n },\n {\n cmd: 'vibecontrols sessions list',\n desc: tr('gettingStarted.cmdSessionsList', 'List workspace sessions'),\n },\n {\n cmd: 'vibecontrols tags create --key bug --label Bug',\n desc: tr('gettingStarted.cmdTagsCreate', 'Create a workspace tag'),\n },\n {\n cmd: 'vibecontrols rbac check <user> read project',\n desc: tr('gettingStarted.cmdRbacCheck', 'Check RBAC access'),\n },\n {\n cmd: 'vibecontrols products feature-flags list',\n desc: tr('gettingStarted.cmdFeatureFlagsList', 'List product feature flags'),\n },\n {\n cmd: 'vibecontrols devportal apps list --status PUBLISHED',\n desc: tr(\n 'gettingStarted.cmdDevportalApps',\n 'List published Developer Portal apps'\n ),\n },\n {\n cmd: 'vibecontrols billing subscriptions list',\n desc: tr('gettingStarted.cmdBillingSubscriptions', 'List billing subscriptions'),\n },\n {\n cmd: 'vibecontrols health check',\n desc: tr('gettingStarted.cmdHealthCheck', 'Run platform health checks'),\n },\n {\n cmd: 'vibecontrols auth logout',\n desc: tr('gettingStarted.cmdAuthLogout', 'Sign out of the CLI'),\n },\n ].map((item) => (\n <MotionItem\n key={item.cmd}\n className=\"flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3 px-3 py-2 rounded-button hover:bg-bg-sunken transition-colors\"\n >\n <code className=\"text-xs font-mono text-text-primary sm:w-72 flex-shrink-0\">\n $ {item.cmd}\n </code>\n <span className=\"text-xs text-text-muted\">{item.desc}</span>\n </MotionItem>\n ))}\n </MotionList>\n <div className=\"mt-4 pt-4 border-t border-border-subtle flex flex-wrap items-center gap-3\">\n <a\n href={CLI_NPM_URL}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-xs border border-border-default text-text-secondary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-colors\"\n >\n {tr('gettingStarted.cliNpmPackage', 'CLI npm package')}{' '}\n <ExternalLink className=\"size-3\" />\n </a>\n <span className=\"text-xs text-text-muted\">\n {tr('gettingStarted.cliNpmLatest', '@vibecontrols/cli (latest)')}\n </span>\n </div>\n </div>\n </Card>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}
@@ -114,7 +114,7 @@ function T() {
114
114
  children: /* @__PURE__ */ x("div", {
115
115
  className: "text-center space-y-2",
116
116
  children: [/* @__PURE__ */ b("h2", {
117
- className: "text-lg font-semibold text-foreground",
117
+ className: "text-lg font-semibold text-text-primary",
118
118
  children: "Access Denied"
119
119
  }), /* @__PURE__ */ b("p", {
120
120
  className: "text-sm text-text-secondary",
@@ -140,12 +140,12 @@ function T() {
140
140
  case "healthy":
141
141
  case "connected":
142
142
  case "RUNNING":
143
- case "ACTIVE": return "bg-status-success-bg text-status-success-text";
143
+ case "ACTIVE": return "bg-status-success-bg-subtle text-status-success-text";
144
144
  case "degraded":
145
145
  case "STARTING":
146
- case "STOPPING": return "bg-status-warning-bg text-status-warning-text";
146
+ case "STOPPING": return "bg-status-warning-bg-subtle text-status-warning-text";
147
147
  case "unhealthy":
148
- case "ERROR": return "bg-status-error-bg text-status-error-text";
148
+ case "ERROR": return "bg-status-error-bg-subtle text-status-error-text";
149
149
  default: return "bg-bg-sunken text-text-secondary";
150
150
  }
151
151
  }, ie = (e) => {
@@ -174,7 +174,7 @@ function T() {
174
174
  connections: K.targets.reduce((e, t) => e + t.agentCount, 0)
175
175
  };
176
176
  return /* @__PURE__ */ x(w, {
177
- className: "min-h-screen space-y-6 bg-bg-surface p-4 sm:p-6",
177
+ className: "min-h-screen space-y-6 bg-bg-canvas px-6 lg:px-8 py-6",
178
178
  children: [
179
179
  /* @__PURE__ */ b(i, {
180
180
  title: G("nav.health", "Health"),
@@ -195,7 +195,7 @@ function T() {
195
195
  type: "button",
196
196
  onClick: () => void q(),
197
197
  disabled: A,
198
- className: "flex items-center gap-2 rounded-md bg-bg-sunken px-4 py-2 text-text-primary transition-colors hover:bg-bg-sunken/80 disabled:opacity-50",
198
+ className: "flex items-center gap-2 rounded-button bg-bg-sunken px-4 py-2 text-text-primary transition-colors hover:bg-action-secondary-bg-hover disabled:opacity-50",
199
199
  children: [/* @__PURE__ */ b(m, { className: `size-4 ${A ? "animate-spin" : ""}` }), G("common.refresh", "Refresh")]
200
200
  })]
201
201
  })
@@ -271,7 +271,7 @@ function T() {
271
271
  /* @__PURE__ */ x(a, {
272
272
  "data-tour": "health-agent-list",
273
273
  children: [/* @__PURE__ */ b("div", {
274
- className: "border-b border-border-default p-4",
274
+ className: "border-b border-border-subtle p-4",
275
275
  children: /* @__PURE__ */ x("h3", {
276
276
  className: "flex items-center gap-2 font-semibold text-text-primary",
277
277
  children: [
@@ -284,7 +284,7 @@ function T() {
284
284
  })
285
285
  }), K.agents.length > 0 ? /* @__PURE__ */ b(C, {
286
286
  stagger: .04,
287
- className: "divide-y divide-border-default",
287
+ className: "divide-y divide-border-subtle",
288
288
  children: K.agents.map((e) => /* @__PURE__ */ b(S, {
289
289
  className: "p-4",
290
290
  children: /* @__PURE__ */ x("div", {
@@ -355,7 +355,7 @@ function T() {
355
355
  /* @__PURE__ */ x("div", {
356
356
  className: "grid grid-cols-1 gap-6 xl:grid-cols-2",
357
357
  children: [/* @__PURE__ */ x(a, { children: [/* @__PURE__ */ b("div", {
358
- className: "border-b border-border-default p-4",
358
+ className: "border-b border-border-subtle p-4",
359
359
  children: /* @__PURE__ */ x("h3", {
360
360
  className: "flex items-center gap-2 font-semibold text-text-primary",
361
361
  children: [
@@ -368,7 +368,7 @@ function T() {
368
368
  })
369
369
  }), K.sessions.length > 0 ? /* @__PURE__ */ b(C, {
370
370
  stagger: .04,
371
- className: "divide-y divide-border-default",
371
+ className: "divide-y divide-border-subtle",
372
372
  children: K.sessions.map((e) => /* @__PURE__ */ x(S, {
373
373
  className: "flex items-center justify-between p-4",
374
374
  children: [/* @__PURE__ */ x("div", {
@@ -389,7 +389,7 @@ function T() {
389
389
  className: "p-6 text-center text-text-secondary",
390
390
  children: G("health.noSessions", "No sessions")
391
391
  })] }), /* @__PURE__ */ x(a, { children: [/* @__PURE__ */ b("div", {
392
- className: "border-b border-border-default p-4",
392
+ className: "border-b border-border-subtle p-4",
393
393
  children: /* @__PURE__ */ x("h3", {
394
394
  className: "flex items-center gap-2 font-semibold text-text-primary",
395
395
  children: [
@@ -402,7 +402,7 @@ function T() {
402
402
  })
403
403
  }), K.vibes.length > 0 ? /* @__PURE__ */ b(C, {
404
404
  stagger: .04,
405
- className: "divide-y divide-border-default",
405
+ className: "divide-y divide-border-subtle",
406
406
  children: K.vibes.map((e) => /* @__PURE__ */ x(S, {
407
407
  className: "flex items-center justify-between p-4",
408
408
  children: [/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"HealthPage.js","names":[],"sources":["../../src/pages/HealthPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n AlertTriangle,\n CheckCircle2,\n RefreshCw,\n Server,\n Terminal,\n XCircle,\n Zap,\n} from 'lucide-react';\nimport { MotionItem, MotionList, MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AgentSetupBanner, Card, PageHeader, StatCard } from '@/components/shared';\nimport { useVibecontrolsAgentHealthProbesQuery } from '@/generated/wspace-operations';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport { useListTargetsQuery, type ListTargetsQuery } from '@/generated/wspace-operations';\n\ntype TargetItem = NonNullable<ListTargetsQuery['targets']['items'][number]>;\n\nconst HEALTH_AUTO_REFRESH_MS = 30000;\n\ntype AgentHealthProbe = NonNullable<\n ReturnType<typeof useVibecontrolsAgentHealthProbesQuery>['data']\n>['vibecontrolsAgentHealthProbes'][number];\n\nexport function HealthPage() {\n const { t } = useI18n();\n const { workspaceId } = useVibeControls();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canListAgents = hasPermission(VIBE_PERMISSIONS.AGENT_LIST);\n\n const [isRefreshing, setIsRefreshing] = useState(false);\n const [lastRefresh, setLastRefresh] = useState(new Date());\n const [autoRefresh, setAutoRefresh] = useState(true);\n\n const {\n agents: cachedAgents,\n sessions: cachedSessions,\n vibes: cachedVibes,\n refetch,\n } = useCachedInitialData();\n const { data: targetsData, refetch: refetchTargets } = useListTargetsQuery({\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n const cachedTargets = targetsData?.targets?.items ?? [];\n\n const healthProbesQuery = useVibecontrolsAgentHealthProbesQuery({\n skip: permissionsLoading || !canListAgents,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n const refetchHealthProbes = healthProbesQuery.refetch;\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const healthData = useMemo(() => {\n const probeByAgentId = new Map<string, AgentHealthProbe>(\n (healthProbesQuery.data?.vibecontrolsAgentHealthProbes ?? []).map(\n (probe: AgentHealthProbe) => [probe.agentId, probe]\n )\n );\n\n const agents = cachedAgents.map((agent) => {\n const probe = probeByAgentId.get(agent.id);\n const isActive = probe ? probe.healthy : agent.isActive;\n let status: 'healthy' | 'degraded' | 'offline' = 'offline';\n\n if (probe) {\n status = probe.healthy ? 'healthy' : 'degraded';\n } else if (isActive && agent.lastHeartbeat) {\n const heartbeatAge = Date.now() - new Date(agent.lastHeartbeat).getTime();\n status = heartbeatAge < 5 * 60 * 1000 ? 'healthy' : 'degraded';\n } else if (isActive) {\n status = 'degraded';\n }\n\n return {\n id: agent.id,\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n version: probe?.agentVersion || agent.version || 'unknown',\n status,\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: probe?.tunnelUrl ?? agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n // Surface the live deep-health failure reason + uptime (previously\n // fetched by the probe query but never rendered) so a degraded/unhealthy\n // agent shows WHY.\n errorMessage: probe?.errorMessage ?? null,\n uptime: probe?.uptime ?? null,\n };\n });\n\n const sessions = cachedSessions.map((session) => ({\n id: session.id,\n name: session.name,\n status: session.status,\n type: session.type || 'TERMINAL',\n }));\n\n const vibes = cachedVibes.map((vibe) => ({\n id: vibe.id,\n name: vibe.name,\n status: vibe.status,\n type: vibe.type || 'PROJECT',\n }));\n\n const targets = cachedTargets.map((target: TargetItem) => ({\n id: target.id,\n name: target.name,\n status: target.status,\n type: target.type || 'DIRECT',\n agentCount: target.agents?.length ?? 0,\n }));\n\n const healthyAgents = agents.filter((agent) => agent.status === 'healthy').length;\n const totalAgents = agents.length;\n const overall =\n totalAgents === 0\n ? 'healthy'\n : healthyAgents === totalAgents\n ? 'healthy'\n : healthyAgents > 0\n ? 'degraded'\n : 'unhealthy';\n\n return { overall, agents, sessions, vibes, targets };\n }, [cachedAgents, cachedSessions, cachedTargets, cachedVibes, healthProbesQuery.data]);\n\n useEffect(() => {\n if (!autoRefresh) {\n return undefined;\n }\n\n const intervalId = window.setInterval(() => {\n void refetch();\n void refetchHealthProbes();\n void refetchTargets();\n setLastRefresh(new Date());\n }, HEALTH_AUTO_REFRESH_MS);\n\n return () => window.clearInterval(intervalId);\n }, [autoRefresh, refetch, refetchHealthProbes, refetchTargets]);\n\n const handleRefresh = useCallback(async () => {\n setIsRefreshing(true);\n try {\n // The three refetches are independent — run them in parallel (matching\n // the auto-refresh interval above) instead of awaiting sequentially.\n await Promise.all([refetch(), refetchHealthProbes(), refetchTargets()]);\n setLastRefresh(new Date());\n } finally {\n setIsRefreshing(false);\n }\n }, [refetch, refetchHealthProbes, refetchTargets]);\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canListAgents)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n const getStatusIcon = (status: string) => {\n switch (status) {\n case 'healthy':\n case 'connected':\n case 'RUNNING':\n case 'ACTIVE':\n return <CheckCircle2 className=\"size-5 text-status-success-text\" />;\n case 'degraded':\n case 'STARTING':\n case 'STOPPING':\n return <AlertTriangle className=\"size-5 text-status-warning-text\" />;\n case 'unhealthy':\n case 'ERROR':\n return <XCircle className=\"size-5 text-status-error-text\" />;\n default:\n return <XCircle className=\"size-5 text-text-secondary\" />;\n }\n };\n\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'healthy':\n case 'connected':\n case 'RUNNING':\n case 'ACTIVE':\n return 'bg-status-success-bg text-status-success-text';\n case 'degraded':\n case 'STARTING':\n case 'STOPPING':\n return 'bg-status-warning-bg text-status-warning-text';\n case 'unhealthy':\n case 'ERROR':\n return 'bg-status-error-bg text-status-error-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n };\n\n const formatTimestamp = (timestamp: string | null) => {\n if (!timestamp) return tr('health.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('health.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleString();\n };\n\n // Probe `uptime` is reported in seconds. Render it compactly so a healthy\n // agent surfaces how long it has been up.\n const formatUptime = (seconds: number): string => {\n if (seconds < 60) return `${Math.round(seconds)}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ${minutes % 60}m`;\n const days = Math.floor(hours / 24);\n return `${days}d ${hours % 24}h`;\n };\n\n const agentStats = {\n healthy: healthData.agents.filter((agent) => agent.status === 'healthy').length,\n degraded: healthData.agents.filter((agent) => agent.status === 'degraded').length,\n total: healthData.agents.length,\n };\n const sessionStats = {\n running: healthData.sessions.filter((session) => session.status === 'RUNNING').length,\n total: healthData.sessions.length,\n };\n const vibeStats = {\n active: healthData.vibes.filter((vibe) => vibe.status === 'ACTIVE' || vibe.status === 'RUNNING')\n .length,\n total: healthData.vibes.length,\n };\n type HealthTarget = {\n id: string;\n name: string;\n status: string;\n type: string;\n agentCount: number;\n };\n const targetStats = {\n healthy: healthData.targets.filter(\n (target: HealthTarget) => target.status === 'ACTIVE' || target.status === 'ONLINE'\n ).length,\n total: healthData.targets.length,\n connections: healthData.targets.reduce(\n (sum: number, target: HealthTarget) => sum + target.agentCount,\n 0\n ),\n };\n\n return (\n <MotionPage className=\"min-h-screen space-y-6 bg-bg-surface p-4 sm:p-6\">\n <PageHeader\n title={tr('nav.health', 'Health')}\n description={`${tr('health.lastUpdated', 'Last updated')}: ${lastRefresh.toLocaleTimeString()} · ${tr('health.autoRefreshInterval', 'Auto-refresh every 30s')}`}\n actions={\n <div\n data-tour=\"health-refresh-controls\"\n className=\"flex flex-col items-start gap-3 sm:flex-row sm:items-center\"\n >\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n aria-label={tr('health.autoRefresh', 'Auto-refresh')}\n type=\"checkbox\"\n checked={autoRefresh}\n onChange={(event) => setAutoRefresh(event.target.checked)}\n className=\"size-4 rounded border-border-default\"\n />\n {tr('health.autoRefresh', 'Auto-refresh')}\n </label>\n <button\n type=\"button\"\n onClick={() => void handleRefresh()}\n disabled={isRefreshing}\n className=\"flex items-center gap-2 rounded-md bg-bg-sunken px-4 py-2 text-text-primary transition-colors hover:bg-bg-sunken/80 disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${isRefreshing ? 'animate-spin' : ''}`} />\n {tr('common.refresh', 'Refresh')}\n </button>\n </div>\n }\n />\n\n <Card data-tour=\"health-overall-status\" className=\"p-6\">\n <div className=\"flex items-center gap-4\">\n <div\n className={`flex size-16 items-center justify-center rounded-full ${getStatusColor(healthData.overall)}`}\n >\n {getStatusIcon(healthData.overall)}\n </div>\n <div>\n <h2 className=\"text-lg font-bold capitalize text-text-primary sm:text-2xl\">\n {tr('health.systemStatus', 'System')} {healthData.overall}\n </h2>\n <p className=\"text-text-secondary\">\n {agentStats.healthy}/{agentStats.total} agents healthy, {sessionStats.running}{' '}\n sessions running, {vibeStats.active} vibes active, {targetStats.connections} target\n connections\n </p>\n </div>\n </div>\n </Card>\n\n <MotionList\n data-tour=\"health-stat-cards\"\n stagger={0.04}\n className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\"\n >\n <MotionItem>\n <StatCard\n title={tr('health.agents', 'Agents')}\n value={`${agentStats.healthy}/${agentStats.total}`}\n subtitle={\n agentStats.degraded > 0\n ? `${agentStats.degraded} degraded`\n : tr('health.healthy', 'healthy')\n }\n icon={<Server className=\"size-6\" />}\n variant={agentStats.healthy === agentStats.total ? 'success' : 'warning'}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('health.sessions', 'Sessions')}\n value={`${sessionStats.running}/${sessionStats.total}`}\n subtitle={tr('health.running', 'running')}\n icon={<Terminal className=\"size-6\" />}\n variant={sessionStats.running > 0 ? 'success' : 'default'}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('health.vibes', 'Vibes')}\n value={`${vibeStats.active}/${vibeStats.total}`}\n subtitle={tr('health.active', 'active')}\n icon={<Zap className=\"size-6\" />}\n variant={vibeStats.active > 0 ? 'success' : 'default'}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('health.targets', 'Targets')}\n value={`${targetStats.healthy}/${targetStats.total}`}\n subtitle={`${targetStats.connections} connections`}\n icon={<Server className=\"size-6\" />}\n variant={targetStats.healthy > 0 ? 'success' : 'default'}\n />\n </MotionItem>\n </MotionList>\n\n <Card data-tour=\"health-agent-list\">\n <div className=\"border-b border-border-default p-4\">\n <h3 className=\"flex items-center gap-2 font-semibold text-text-primary\">\n <Server className=\"size-5\" />\n {tr('health.agentHealth', 'Agent Health')} ({healthData.agents.length})\n </h3>\n </div>\n {healthData.agents.length > 0 ? (\n <MotionList stagger={0.04} className=\"divide-y divide-border-default\">\n {healthData.agents.map((agent) => (\n <MotionItem key={agent.id} className=\"p-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n {getStatusIcon(agent.status)}\n <div>\n <div className=\"font-medium text-text-primary\">{agent.name}</div>\n <div className=\"text-sm text-text-secondary\">\n {agent.hostname} · {agent.platform} · v{agent.version}\n </div>\n <div className=\"mt-0.5 text-xs text-text-secondary\">\n {tr('health.lastHeartbeat', 'Last heartbeat')}:{' '}\n {formatTimestamp(agent.lastHeartbeat)}\n {agent.uptime != null && (\n <>\n {' · '}\n {tr('health.uptime', 'Uptime')}: {formatUptime(agent.uptime)}\n </>\n )}\n </div>\n {agent.errorMessage && agent.status !== 'healthy' && (\n <div className=\"mt-1 text-xs text-status-error-text\">\n {agent.errorMessage}\n </div>\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n {agent.tunnelUrl ? (\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-xs text-action-primary-text hover:underline\"\n >\n {tr('health.tunnel', 'Tunnel')}\n </a>\n ) : null}\n <span\n className={`rounded-full px-3 py-1 text-sm capitalize ${getStatusColor(agent.status)}`}\n >\n {agent.status}\n </span>\n </div>\n </div>\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"space-y-4 p-6\">\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-health-setup-banner\" />\n <p className=\"text-center text-text-secondary\">\n {tr('health.noAgentsRegistered', 'No agents registered')}\n </p>\n </div>\n )}\n </Card>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <Card>\n <div className=\"border-b border-border-default p-4\">\n <h3 className=\"flex items-center gap-2 font-semibold text-text-primary\">\n <Terminal className=\"size-5\" />\n {tr('health.sessionStatus', 'Session Status')} ({healthData.sessions.length})\n </h3>\n </div>\n {healthData.sessions.length > 0 ? (\n <MotionList stagger={0.04} className=\"divide-y divide-border-default\">\n {healthData.sessions.map((session) => (\n <MotionItem key={session.id} className=\"flex items-center justify-between p-4\">\n <div className=\"flex items-center gap-3\">\n {getStatusIcon(session.status)}\n <div>\n <div className=\"font-medium text-text-primary\">{session.name}</div>\n <div className=\"text-sm text-text-secondary\">{session.type}</div>\n </div>\n </div>\n <span\n className={`rounded-full px-3 py-1 text-sm ${getStatusColor(session.status)}`}\n >\n {session.status}\n </span>\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"p-6 text-center text-text-secondary\">\n {tr('health.noSessions', 'No sessions')}\n </div>\n )}\n </Card>\n\n <Card>\n <div className=\"border-b border-border-default p-4\">\n <h3 className=\"flex items-center gap-2 font-semibold text-text-primary\">\n <Zap className=\"size-5\" />\n {tr('health.vibeStatus', 'Vibe Status')} ({healthData.vibes.length})\n </h3>\n </div>\n {healthData.vibes.length > 0 ? (\n <MotionList stagger={0.04} className=\"divide-y divide-border-default\">\n {healthData.vibes.map((vibe) => (\n <MotionItem key={vibe.id} className=\"flex items-center justify-between p-4\">\n <div>\n <div className=\"font-medium text-text-primary\">{vibe.name}</div>\n <div className=\"text-sm text-text-secondary\">{vibe.type}</div>\n </div>\n <span className={`rounded-full px-3 py-1 text-sm ${getStatusColor(vibe.status)}`}>\n {vibe.status}\n </span>\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"p-6 text-center text-text-secondary\">\n {tr('health.noVibes', 'No vibes')}\n </div>\n )}\n </Card>\n </div>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HealthPage.js","names":[],"sources":["../../src/pages/HealthPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n AlertTriangle,\n CheckCircle2,\n RefreshCw,\n Server,\n Terminal,\n XCircle,\n Zap,\n} from 'lucide-react';\nimport { MotionItem, MotionList, MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AgentSetupBanner, Card, PageHeader, StatCard } from '@/components/shared';\nimport { useVibecontrolsAgentHealthProbesQuery } from '@/generated/wspace-operations';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport { useListTargetsQuery, type ListTargetsQuery } from '@/generated/wspace-operations';\n\ntype TargetItem = NonNullable<ListTargetsQuery['targets']['items'][number]>;\n\nconst HEALTH_AUTO_REFRESH_MS = 30000;\n\ntype AgentHealthProbe = NonNullable<\n ReturnType<typeof useVibecontrolsAgentHealthProbesQuery>['data']\n>['vibecontrolsAgentHealthProbes'][number];\n\nexport function HealthPage() {\n const { t } = useI18n();\n const { workspaceId } = useVibeControls();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canListAgents = hasPermission(VIBE_PERMISSIONS.AGENT_LIST);\n\n const [isRefreshing, setIsRefreshing] = useState(false);\n const [lastRefresh, setLastRefresh] = useState(new Date());\n const [autoRefresh, setAutoRefresh] = useState(true);\n\n const {\n agents: cachedAgents,\n sessions: cachedSessions,\n vibes: cachedVibes,\n refetch,\n } = useCachedInitialData();\n const { data: targetsData, refetch: refetchTargets } = useListTargetsQuery({\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n const cachedTargets = targetsData?.targets?.items ?? [];\n\n const healthProbesQuery = useVibecontrolsAgentHealthProbesQuery({\n skip: permissionsLoading || !canListAgents,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n const refetchHealthProbes = healthProbesQuery.refetch;\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const healthData = useMemo(() => {\n const probeByAgentId = new Map<string, AgentHealthProbe>(\n (healthProbesQuery.data?.vibecontrolsAgentHealthProbes ?? []).map(\n (probe: AgentHealthProbe) => [probe.agentId, probe]\n )\n );\n\n const agents = cachedAgents.map((agent) => {\n const probe = probeByAgentId.get(agent.id);\n const isActive = probe ? probe.healthy : agent.isActive;\n let status: 'healthy' | 'degraded' | 'offline' = 'offline';\n\n if (probe) {\n status = probe.healthy ? 'healthy' : 'degraded';\n } else if (isActive && agent.lastHeartbeat) {\n const heartbeatAge = Date.now() - new Date(agent.lastHeartbeat).getTime();\n status = heartbeatAge < 5 * 60 * 1000 ? 'healthy' : 'degraded';\n } else if (isActive) {\n status = 'degraded';\n }\n\n return {\n id: agent.id,\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n version: probe?.agentVersion || agent.version || 'unknown',\n status,\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: probe?.tunnelUrl ?? agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n // Surface the live deep-health failure reason + uptime (previously\n // fetched by the probe query but never rendered) so a degraded/unhealthy\n // agent shows WHY.\n errorMessage: probe?.errorMessage ?? null,\n uptime: probe?.uptime ?? null,\n };\n });\n\n const sessions = cachedSessions.map((session) => ({\n id: session.id,\n name: session.name,\n status: session.status,\n type: session.type || 'TERMINAL',\n }));\n\n const vibes = cachedVibes.map((vibe) => ({\n id: vibe.id,\n name: vibe.name,\n status: vibe.status,\n type: vibe.type || 'PROJECT',\n }));\n\n const targets = cachedTargets.map((target: TargetItem) => ({\n id: target.id,\n name: target.name,\n status: target.status,\n type: target.type || 'DIRECT',\n agentCount: target.agents?.length ?? 0,\n }));\n\n const healthyAgents = agents.filter((agent) => agent.status === 'healthy').length;\n const totalAgents = agents.length;\n const overall =\n totalAgents === 0\n ? 'healthy'\n : healthyAgents === totalAgents\n ? 'healthy'\n : healthyAgents > 0\n ? 'degraded'\n : 'unhealthy';\n\n return { overall, agents, sessions, vibes, targets };\n }, [cachedAgents, cachedSessions, cachedTargets, cachedVibes, healthProbesQuery.data]);\n\n useEffect(() => {\n if (!autoRefresh) {\n return undefined;\n }\n\n const intervalId = window.setInterval(() => {\n void refetch();\n void refetchHealthProbes();\n void refetchTargets();\n setLastRefresh(new Date());\n }, HEALTH_AUTO_REFRESH_MS);\n\n return () => window.clearInterval(intervalId);\n }, [autoRefresh, refetch, refetchHealthProbes, refetchTargets]);\n\n const handleRefresh = useCallback(async () => {\n setIsRefreshing(true);\n try {\n // The three refetches are independent — run them in parallel (matching\n // the auto-refresh interval above) instead of awaiting sequentially.\n await Promise.all([refetch(), refetchHealthProbes(), refetchTargets()]);\n setLastRefresh(new Date());\n } finally {\n setIsRefreshing(false);\n }\n }, [refetch, refetchHealthProbes, refetchTargets]);\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canListAgents)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n const getStatusIcon = (status: string) => {\n switch (status) {\n case 'healthy':\n case 'connected':\n case 'RUNNING':\n case 'ACTIVE':\n return <CheckCircle2 className=\"size-5 text-status-success-text\" />;\n case 'degraded':\n case 'STARTING':\n case 'STOPPING':\n return <AlertTriangle className=\"size-5 text-status-warning-text\" />;\n case 'unhealthy':\n case 'ERROR':\n return <XCircle className=\"size-5 text-status-error-text\" />;\n default:\n return <XCircle className=\"size-5 text-text-secondary\" />;\n }\n };\n\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'healthy':\n case 'connected':\n case 'RUNNING':\n case 'ACTIVE':\n return 'bg-status-success-bg-subtle text-status-success-text';\n case 'degraded':\n case 'STARTING':\n case 'STOPPING':\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n case 'unhealthy':\n case 'ERROR':\n return 'bg-status-error-bg-subtle text-status-error-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n };\n\n const formatTimestamp = (timestamp: string | null) => {\n if (!timestamp) return tr('health.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('health.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleString();\n };\n\n // Probe `uptime` is reported in seconds. Render it compactly so a healthy\n // agent surfaces how long it has been up.\n const formatUptime = (seconds: number): string => {\n if (seconds < 60) return `${Math.round(seconds)}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ${minutes % 60}m`;\n const days = Math.floor(hours / 24);\n return `${days}d ${hours % 24}h`;\n };\n\n const agentStats = {\n healthy: healthData.agents.filter((agent) => agent.status === 'healthy').length,\n degraded: healthData.agents.filter((agent) => agent.status === 'degraded').length,\n total: healthData.agents.length,\n };\n const sessionStats = {\n running: healthData.sessions.filter((session) => session.status === 'RUNNING').length,\n total: healthData.sessions.length,\n };\n const vibeStats = {\n active: healthData.vibes.filter((vibe) => vibe.status === 'ACTIVE' || vibe.status === 'RUNNING')\n .length,\n total: healthData.vibes.length,\n };\n type HealthTarget = {\n id: string;\n name: string;\n status: string;\n type: string;\n agentCount: number;\n };\n const targetStats = {\n healthy: healthData.targets.filter(\n (target: HealthTarget) => target.status === 'ACTIVE' || target.status === 'ONLINE'\n ).length,\n total: healthData.targets.length,\n connections: healthData.targets.reduce(\n (sum: number, target: HealthTarget) => sum + target.agentCount,\n 0\n ),\n };\n\n return (\n <MotionPage className=\"min-h-screen space-y-6 bg-bg-canvas px-6 lg:px-8 py-6\">\n <PageHeader\n title={tr('nav.health', 'Health')}\n description={`${tr('health.lastUpdated', 'Last updated')}: ${lastRefresh.toLocaleTimeString()} · ${tr('health.autoRefreshInterval', 'Auto-refresh every 30s')}`}\n actions={\n <div\n data-tour=\"health-refresh-controls\"\n className=\"flex flex-col items-start gap-3 sm:flex-row sm:items-center\"\n >\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n aria-label={tr('health.autoRefresh', 'Auto-refresh')}\n type=\"checkbox\"\n checked={autoRefresh}\n onChange={(event) => setAutoRefresh(event.target.checked)}\n className=\"size-4 rounded border-border-default\"\n />\n {tr('health.autoRefresh', 'Auto-refresh')}\n </label>\n <button\n type=\"button\"\n onClick={() => void handleRefresh()}\n disabled={isRefreshing}\n className=\"flex items-center gap-2 rounded-button bg-bg-sunken px-4 py-2 text-text-primary transition-colors hover:bg-action-secondary-bg-hover disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${isRefreshing ? 'animate-spin' : ''}`} />\n {tr('common.refresh', 'Refresh')}\n </button>\n </div>\n }\n />\n\n <Card data-tour=\"health-overall-status\" className=\"p-6\">\n <div className=\"flex items-center gap-4\">\n <div\n className={`flex size-16 items-center justify-center rounded-full ${getStatusColor(healthData.overall)}`}\n >\n {getStatusIcon(healthData.overall)}\n </div>\n <div>\n <h2 className=\"text-lg font-bold capitalize text-text-primary sm:text-2xl\">\n {tr('health.systemStatus', 'System')} {healthData.overall}\n </h2>\n <p className=\"text-text-secondary\">\n {agentStats.healthy}/{agentStats.total} agents healthy, {sessionStats.running}{' '}\n sessions running, {vibeStats.active} vibes active, {targetStats.connections} target\n connections\n </p>\n </div>\n </div>\n </Card>\n\n <MotionList\n data-tour=\"health-stat-cards\"\n stagger={0.04}\n className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\"\n >\n <MotionItem>\n <StatCard\n title={tr('health.agents', 'Agents')}\n value={`${agentStats.healthy}/${agentStats.total}`}\n subtitle={\n agentStats.degraded > 0\n ? `${agentStats.degraded} degraded`\n : tr('health.healthy', 'healthy')\n }\n icon={<Server className=\"size-6\" />}\n variant={agentStats.healthy === agentStats.total ? 'success' : 'warning'}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('health.sessions', 'Sessions')}\n value={`${sessionStats.running}/${sessionStats.total}`}\n subtitle={tr('health.running', 'running')}\n icon={<Terminal className=\"size-6\" />}\n variant={sessionStats.running > 0 ? 'success' : 'default'}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('health.vibes', 'Vibes')}\n value={`${vibeStats.active}/${vibeStats.total}`}\n subtitle={tr('health.active', 'active')}\n icon={<Zap className=\"size-6\" />}\n variant={vibeStats.active > 0 ? 'success' : 'default'}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('health.targets', 'Targets')}\n value={`${targetStats.healthy}/${targetStats.total}`}\n subtitle={`${targetStats.connections} connections`}\n icon={<Server className=\"size-6\" />}\n variant={targetStats.healthy > 0 ? 'success' : 'default'}\n />\n </MotionItem>\n </MotionList>\n\n <Card data-tour=\"health-agent-list\">\n <div className=\"border-b border-border-subtle p-4\">\n <h3 className=\"flex items-center gap-2 font-semibold text-text-primary\">\n <Server className=\"size-5\" />\n {tr('health.agentHealth', 'Agent Health')} ({healthData.agents.length})\n </h3>\n </div>\n {healthData.agents.length > 0 ? (\n <MotionList stagger={0.04} className=\"divide-y divide-border-subtle\">\n {healthData.agents.map((agent) => (\n <MotionItem key={agent.id} className=\"p-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n {getStatusIcon(agent.status)}\n <div>\n <div className=\"font-medium text-text-primary\">{agent.name}</div>\n <div className=\"text-sm text-text-secondary\">\n {agent.hostname} · {agent.platform} · v{agent.version}\n </div>\n <div className=\"mt-0.5 text-xs text-text-secondary\">\n {tr('health.lastHeartbeat', 'Last heartbeat')}:{' '}\n {formatTimestamp(agent.lastHeartbeat)}\n {agent.uptime != null && (\n <>\n {' · '}\n {tr('health.uptime', 'Uptime')}: {formatUptime(agent.uptime)}\n </>\n )}\n </div>\n {agent.errorMessage && agent.status !== 'healthy' && (\n <div className=\"mt-1 text-xs text-status-error-text\">\n {agent.errorMessage}\n </div>\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n {agent.tunnelUrl ? (\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-xs text-action-primary-text hover:underline\"\n >\n {tr('health.tunnel', 'Tunnel')}\n </a>\n ) : null}\n <span\n className={`rounded-full px-3 py-1 text-sm capitalize ${getStatusColor(agent.status)}`}\n >\n {agent.status}\n </span>\n </div>\n </div>\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"space-y-4 p-6\">\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-health-setup-banner\" />\n <p className=\"text-center text-text-secondary\">\n {tr('health.noAgentsRegistered', 'No agents registered')}\n </p>\n </div>\n )}\n </Card>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <Card>\n <div className=\"border-b border-border-subtle p-4\">\n <h3 className=\"flex items-center gap-2 font-semibold text-text-primary\">\n <Terminal className=\"size-5\" />\n {tr('health.sessionStatus', 'Session Status')} ({healthData.sessions.length})\n </h3>\n </div>\n {healthData.sessions.length > 0 ? (\n <MotionList stagger={0.04} className=\"divide-y divide-border-subtle\">\n {healthData.sessions.map((session) => (\n <MotionItem key={session.id} className=\"flex items-center justify-between p-4\">\n <div className=\"flex items-center gap-3\">\n {getStatusIcon(session.status)}\n <div>\n <div className=\"font-medium text-text-primary\">{session.name}</div>\n <div className=\"text-sm text-text-secondary\">{session.type}</div>\n </div>\n </div>\n <span\n className={`rounded-full px-3 py-1 text-sm ${getStatusColor(session.status)}`}\n >\n {session.status}\n </span>\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"p-6 text-center text-text-secondary\">\n {tr('health.noSessions', 'No sessions')}\n </div>\n )}\n </Card>\n\n <Card>\n <div className=\"border-b border-border-subtle p-4\">\n <h3 className=\"flex items-center gap-2 font-semibold text-text-primary\">\n <Zap className=\"size-5\" />\n {tr('health.vibeStatus', 'Vibe Status')} ({healthData.vibes.length})\n </h3>\n </div>\n {healthData.vibes.length > 0 ? (\n <MotionList stagger={0.04} className=\"divide-y divide-border-subtle\">\n {healthData.vibes.map((vibe) => (\n <MotionItem key={vibe.id} className=\"flex items-center justify-between p-4\">\n <div>\n <div className=\"font-medium text-text-primary\">{vibe.name}</div>\n <div className=\"text-sm text-text-secondary\">{vibe.type}</div>\n </div>\n <span className={`rounded-full px-3 py-1 text-sm ${getStatusColor(vibe.status)}`}>\n {vibe.status}\n </span>\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"p-6 text-center text-text-secondary\">\n {tr('health.noVibes', 'No vibes')}\n </div>\n )}\n </Card>\n </div>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
@@ -37,7 +37,7 @@ function v() {
37
37
  children: "Loading permissions…"
38
38
  })
39
39
  }) : y(s.LOG_LIST) ? S && D.length === 0 ? /* @__PURE__ */ g(_, {
40
- className: "flex h-full flex-col gap-4 p-4",
40
+ className: "flex h-full flex-col gap-6 px-6 lg:px-8 py-6",
41
41
  children: [/* @__PURE__ */ h(n, {
42
42
  title: T("nav.logs", "Logs"),
43
43
  description: T("logs.description", "Gateway-proxied log streaming from your agents")
@@ -46,14 +46,14 @@ function v() {
46
46
  count: 6
47
47
  })]
48
48
  }) : /* @__PURE__ */ g(_, {
49
- className: "flex h-full flex-col gap-4 p-4",
49
+ className: "flex h-full flex-col gap-6 px-6 lg:px-8 py-6",
50
50
  children: [
51
51
  /* @__PURE__ */ h(n, {
52
52
  title: T("nav.logs", "Logs"),
53
53
  description: T("logs.description", "Gateway-proxied log streaming from your agents")
54
54
  }),
55
55
  /* @__PURE__ */ g("div", {
56
- className: "flex flex-col items-start gap-3 sm:flex-row sm:items-center",
56
+ className: "flex flex-col items-start gap-3 rounded-card border border-border-subtle bg-bg-surface p-4 shadow-elevation-1 sm:flex-row sm:items-center",
57
57
  children: [
58
58
  /* @__PURE__ */ h("label", {
59
59
  className: "text-sm font-medium text-text-secondary",
@@ -97,7 +97,7 @@ function v() {
97
97
  children: /* @__PURE__ */ g("div", {
98
98
  className: "text-center space-y-2",
99
99
  children: [/* @__PURE__ */ h("h2", {
100
- className: "text-lg font-semibold text-foreground",
100
+ className: "text-lg font-semibold text-text-primary",
101
101
  children: "Access Denied"
102
102
  }), /* @__PURE__ */ h("p", {
103
103
  className: "text-sm text-text-secondary",