@skyhook-io/radar-app 1.17.0 → 1.17.1

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 (276) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/App.tsx +125 -87
  4. package/src/api/actions.errorCode.test.ts +40 -0
  5. package/src/api/actions.test.ts +48 -0
  6. package/src/api/actions.ts +79 -0
  7. package/src/api/apiResources.navigation.test.tsx +232 -0
  8. package/src/api/apiResources.ts +46 -3
  9. package/src/api/client.helmSource.test.ts +39 -0
  10. package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
  11. package/src/api/client.namespaceSwitch.test.ts +107 -0
  12. package/src/api/client.ts +266 -39
  13. package/src/api/cnpg-ha.live.test.ts +140 -0
  14. package/src/api/cnpg-ha.test.ts +78 -0
  15. package/src/api/cnpg-ha.ts +19 -0
  16. package/src/api/cnpg-history.ts +123 -0
  17. package/src/api/cnpg-inspect.ts +102 -0
  18. package/src/api/cnpg-protection.ts +112 -0
  19. package/src/api/cnpg-recovery.test.tsx +60 -0
  20. package/src/api/cnpg-recovery.ts +262 -0
  21. package/src/api/cnpg-sessions.ts +175 -0
  22. package/src/api/cnpg-storage.ts +138 -0
  23. package/src/api/cnpg.legacy-reads.test.ts +43 -0
  24. package/src/api/cnpg.pooler-runtime.test.ts +15 -0
  25. package/src/api/cnpg.ts +428 -10
  26. package/src/api/radarFeatures.render.test.tsx +47 -3
  27. package/src/api/radarFeatures.ts +4 -1
  28. package/src/api/reviewedActionMutation.test.tsx +164 -0
  29. package/src/api/reviewedActionMutation.ts +66 -0
  30. package/src/api/timelineSource.test.ts +9 -0
  31. package/src/api/timelineSource.ts +8 -0
  32. package/src/components/audit/AuditView.tsx +9 -0
  33. package/src/components/capacity/CapacityActivity.tsx +3 -31
  34. package/src/components/capacity/CapacityDemand.tsx +4 -36
  35. package/src/components/capacity/CapacityOverview.tsx +217 -56
  36. package/src/components/capacity/CapacityPoolDetail.tsx +6 -55
  37. package/src/components/capacity/CapacityView.test.tsx +323 -0
  38. package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
  39. package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
  40. package/src/components/capacity/routes.ts +42 -0
  41. package/src/components/capacity/shared.tsx +11 -129
  42. package/src/components/capacity/workspaceRoute.ts +18 -0
  43. package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
  44. package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
  45. package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
  46. package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
  47. package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
  48. package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
  49. package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
  50. package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
  51. package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
  52. package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
  53. package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
  54. package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
  55. package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
  56. package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
  57. package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
  58. package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
  59. package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
  60. package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
  61. package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
  62. package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
  63. package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
  64. package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
  65. package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
  66. package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
  67. package/src/components/cnpg/CNPGOperator.tsx +78 -30
  68. package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
  69. package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
  70. package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
  71. package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
  72. package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
  73. package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
  74. package/src/components/cnpg/CNPGOverview.tsx +238 -63
  75. package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
  76. package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
  77. package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
  78. package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
  79. package/src/components/cnpg/CNPGPerformance.tsx +477 -0
  80. package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
  81. package/src/components/cnpg/CNPGPooling.tsx +97 -40
  82. package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
  83. package/src/components/cnpg/CNPGProtection.tsx +228 -122
  84. package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
  85. package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
  86. package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
  87. package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
  88. package/src/components/cnpg/CNPGStorage.tsx +619 -0
  89. package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
  90. package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
  91. package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
  92. package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
  93. package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
  94. package/src/components/cnpg/CNPGTrends.test.ts +38 -0
  95. package/src/components/cnpg/CNPGTrends.tsx +702 -0
  96. package/src/components/cnpg/CNPGView.tsx +34 -21
  97. package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
  98. package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
  99. package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
  100. package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
  101. package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
  102. package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
  103. package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
  104. package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
  105. package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
  106. package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
  107. package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
  108. package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
  109. package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
  110. package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
  111. package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
  112. package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
  113. package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
  114. package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
  115. package/src/components/cnpg/actions/actionModel.test.ts +111 -0
  116. package/src/components/cnpg/actions/actionModel.ts +127 -0
  117. package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
  118. package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
  119. package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
  120. package/src/components/cnpg/archivingRepair.test.ts +104 -0
  121. package/src/components/cnpg/archivingRepair.ts +145 -0
  122. package/src/components/cnpg/baseBackup.test.ts +36 -0
  123. package/src/components/cnpg/baseBackup.ts +36 -0
  124. package/src/components/cnpg/blocking.test.ts +59 -0
  125. package/src/components/cnpg/blocking.ts +131 -0
  126. package/src/components/cnpg/coverageText.test.ts +27 -0
  127. package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
  128. package/src/components/cnpg/creation/targetModel.test.ts +137 -0
  129. package/src/components/cnpg/creation/targetModel.ts +149 -0
  130. package/src/components/cnpg/fleetStatus.test.ts +46 -0
  131. package/src/components/cnpg/fleetStatus.ts +43 -0
  132. package/src/components/cnpg/formFields.ts +2 -0
  133. package/src/components/cnpg/grantText.test.tsx +11 -0
  134. package/src/components/cnpg/host.test.tsx +24 -0
  135. package/src/components/cnpg/host.tsx +101 -0
  136. package/src/components/cnpg/inspectModel.test.ts +120 -0
  137. package/src/components/cnpg/inspectModel.ts +140 -0
  138. package/src/components/cnpg/logicalSlots.test.ts +30 -0
  139. package/src/components/cnpg/logicalSlots.ts +29 -0
  140. package/src/components/cnpg/lsn.test.ts +40 -0
  141. package/src/components/cnpg/lsn.ts +43 -0
  142. package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
  143. package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
  144. package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
  145. package/src/components/cnpg/operations/model.test.ts +510 -0
  146. package/src/components/cnpg/operations/model.ts +499 -0
  147. package/src/components/cnpg/operations/presentation.test.ts +80 -0
  148. package/src/components/cnpg/operations/presentation.ts +84 -0
  149. package/src/components/cnpg/operations/store.ts +98 -0
  150. package/src/components/cnpg/operatorStatus.test.ts +134 -0
  151. package/src/components/cnpg/operatorStatus.ts +194 -0
  152. package/src/components/cnpg/paths.test.ts +54 -0
  153. package/src/components/cnpg/paths.ts +47 -4
  154. package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
  155. package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
  156. package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
  157. package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
  158. package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
  159. package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
  160. package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
  161. package/src/components/cnpg/protection/scheduleModel.ts +16 -0
  162. package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
  163. package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
  164. package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
  165. package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
  166. package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
  167. package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
  168. package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
  169. package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
  170. package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
  171. package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
  172. package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
  173. package/src/components/cnpg/recovery/restoreModel.ts +685 -0
  174. package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
  175. package/src/components/cnpg/refresh.test.ts +38 -0
  176. package/src/components/cnpg/refresh.ts +17 -0
  177. package/src/components/cnpg/refreshNotice.test.tsx +29 -0
  178. package/src/components/cnpg/routes.test.ts +34 -3
  179. package/src/components/cnpg/routes.ts +60 -48
  180. package/src/components/cnpg/runtimeAssessment.ts +198 -0
  181. package/src/components/cnpg/runtimeModel.test.ts +175 -0
  182. package/src/components/cnpg/runtimeModel.ts +196 -0
  183. package/src/components/cnpg/runtimeParts.test.tsx +17 -0
  184. package/src/components/cnpg/runtimeParts.tsx +79 -0
  185. package/src/components/cnpg/shared.test.tsx +59 -0
  186. package/src/components/cnpg/shared.tsx +100 -182
  187. package/src/components/cnpg/storageModel.test.ts +107 -0
  188. package/src/components/cnpg/storageModel.ts +101 -0
  189. package/src/components/cnpg/trendSamples.test.ts +148 -0
  190. package/src/components/cnpg/trendSamples.ts +222 -0
  191. package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
  192. package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
  193. package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
  194. package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
  195. package/src/components/cnpg/useCNPGNavigate.ts +21 -0
  196. package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
  197. package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
  198. package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
  199. package/src/components/cnpg/workspaceRoute.ts +23 -0
  200. package/src/components/cost/ApplicationCostTab.test.ts +7 -0
  201. package/src/components/cost/ApplicationCostTab.tsx +7 -7
  202. package/src/components/cost/CostView.test.ts +2 -1
  203. package/src/components/cost/CostView.tsx +0 -2
  204. package/src/components/cost/WorkloadCostTab.tsx +2 -4
  205. package/src/components/cost/errors.ts +3 -1
  206. package/src/components/cost/source.test.ts +10 -0
  207. package/src/components/cost/source.ts +5 -1
  208. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
  209. package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
  210. package/src/components/dock/BottomDock.tsx +2 -0
  211. package/src/components/dock/TerminalTab.test.tsx +17 -0
  212. package/src/components/dock/TerminalTab.tsx +7 -3
  213. package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
  214. package/src/components/gitops/GitOpsView.tsx +16 -23
  215. package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
  216. package/src/components/helm/HelmCompareRoute.tsx +57 -26
  217. package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
  218. package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
  219. package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
  220. package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
  221. package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
  222. package/src/components/helm/ValuesViewer.test.tsx +301 -0
  223. package/src/components/helm/ValuesViewer.tsx +169 -78
  224. package/src/components/home/ActivitySummary.tsx +2 -1
  225. package/src/components/home/HomeView.tsx +20 -9
  226. package/src/components/home/ProblemsPanel.test.tsx +81 -0
  227. package/src/components/home/TopologyPreview.tsx +2 -2
  228. package/src/components/issues/IssuesPane.render.test.tsx +122 -0
  229. package/src/components/issues/IssuesPane.test.ts +65 -1
  230. package/src/components/issues/IssuesPane.tsx +188 -25
  231. package/src/components/issues/uncheckedSources.ts +21 -0
  232. package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
  233. package/src/components/resource/PVCUsageBar.test.tsx +24 -0
  234. package/src/components/resource/PVCUsageBar.tsx +15 -6
  235. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
  236. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
  237. package/src/components/resources/CompositeRenderer.tsx +4 -4
  238. package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
  239. package/src/components/resources/ResourcesView.tsx +26 -12
  240. package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
  241. package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
  242. package/src/components/settings/SettingsDialog.tsx +28 -15
  243. package/src/components/timeline/TimelineList.tsx +22 -24
  244. package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
  245. package/src/components/timeline/TimelineLoadError.tsx +96 -0
  246. package/src/components/timeline/TimelineView.tsx +18 -45
  247. package/src/components/timeline/ViewModeToggle.tsx +20 -0
  248. package/src/components/timeline/useStickyError.test.tsx +47 -0
  249. package/src/components/timeline/useStickyError.ts +15 -0
  250. package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
  251. package/src/components/ui/Markdown.test.tsx +65 -0
  252. package/src/components/ui/Markdown.tsx +144 -127
  253. package/src/components/whats-new/releaseNotes.ts +63 -1
  254. package/src/components/workload/WorkloadView.test.ts +10 -1
  255. package/src/components/workload/WorkloadView.tsx +27 -239
  256. package/src/components/workspace/layout.test.tsx +29 -0
  257. package/src/components/workspace/layout.tsx +259 -0
  258. package/src/components/workspace/table.ts +7 -0
  259. package/src/contexts/CapabilitiesContext.tsx +1 -1
  260. package/src/integrations/resourceHost.test.tsx +55 -0
  261. package/src/integrations/resourceHost.ts +99 -0
  262. package/src/integrations/resourceHosts.test.tsx +141 -0
  263. package/src/integrations/resourceHosts.tsx +119 -0
  264. package/src/integrations/workspaceRoute.ts +20 -0
  265. package/src/integrations/workspaceRoutes.test.ts +40 -0
  266. package/src/integrations/workspaceRoutes.ts +22 -0
  267. package/src/integrations/workspaceScreens.tsx +14 -0
  268. package/src/utils/drawer-trail.ts +35 -0
  269. package/src/utils/kindFilterStore.test.ts +147 -0
  270. package/src/utils/kindFilterStore.ts +91 -0
  271. package/src/utils/navigation.test.ts +13 -1
  272. package/src/utils/navigation.ts +39 -2
  273. package/src/utils/page-links.ts +21 -0
  274. package/src/utils/tabStorage.ts +40 -0
  275. package/src/utils/viewMemory.test.ts +78 -0
  276. package/src/utils/viewMemory.ts +54 -0
@@ -1,5 +1,5 @@
1
1
  import type { LucideIcon } from 'lucide-react'
2
- import { Bot, GitCompareArrows, Layers, Network, RefreshCcw, Scale, ScrollText, SlidersHorizontal } from 'lucide-react'
2
+ import { Bot, Database, GitCompareArrows, HardDrive, Hourglass, Layers, Network, RefreshCcw, Scale, ScrollText, SlidersHorizontal, Undo2 } from 'lucide-react'
3
3
  import { compareVersions } from '../../utils/version'
4
4
 
5
5
  export interface ReleaseHighlight {
@@ -47,6 +47,68 @@ export const CHANGELOG_URL = 'https://radarhq.io/changelog'
47
47
  // major release must have its own entry: scripts/check-whats-new.sh refuses to
48
48
  // release one without it.
49
49
  export const RELEASE_NOTES: ReleaseNotes[] = [
50
+ {
51
+ version: 'v1.17.0',
52
+ highlights: [
53
+ {
54
+ id: 'cloudnative-pg',
55
+ icon: Database,
56
+ title: 'CloudNativePG, end to end',
57
+ description: 'See which Postgres cluster needs attention and why, whether its backups would restore, and switch over, fence or restore with a review first.',
58
+ leadDescription: 'New views for CloudNativePG show which Postgres cluster needs attention and why, what each instance is doing, and whether its backups would actually let you restore. Create, restore, back up, switch over, fence and hibernate from Radar: you review exactly what will be written, then Radar follows the operation until it sees the outcome.',
59
+ importance: 9,
60
+ path: '/cnpg',
61
+ cta: 'Open CloudNativePG',
62
+ },
63
+ {
64
+ id: 'live-traffic-large-clusters',
65
+ icon: Network,
66
+ title: 'Live Traffic on large Cilium clusters',
67
+ description: 'Traffic is drawn per workload and stays fast on big clusters. Focus on one workload, or get a connection list when the map is too big.',
68
+ importance: 7,
69
+ path: '/traffic',
70
+ cta: 'Open Live Traffic',
71
+ },
72
+ {
73
+ id: 'gitops-revert-warnings',
74
+ icon: Undo2,
75
+ title: 'Know before an edit gets reverted',
76
+ description: 'Before Radar edits an object owned by Argo CD, Flux or Helm, it tells you whether the owner will undo the change.',
77
+ importance: 6,
78
+ },
79
+ {
80
+ id: 'stuck-deletion-issues',
81
+ icon: Hourglass,
82
+ title: 'Issues for stuck deletions',
83
+ description: "Helm uninstalls and custom resources stuck deleting raise issues, and so do volumes that can't attach to their node's machine type.",
84
+ importance: 6,
85
+ path: '/issues',
86
+ cta: 'Open Issues',
87
+ },
88
+ {
89
+ id: 'audit-unused-storage',
90
+ icon: HardDrive,
91
+ title: 'Cluster Audit finds unused storage',
92
+ description: 'Claims nothing uses, claims Pending for over a day, and Released PersistentVolumes, with their size and storage class.',
93
+ // Below v1.16's headline at its 0.75 weight, so an upgrade from v1.15
94
+ // still shows that card.
95
+ importance: 4,
96
+ path: '/checks',
97
+ cta: 'Open Checks',
98
+ },
99
+ ],
100
+ improvements: [
101
+ // The grid fills row by row: pair lines of similar length.
102
+ "Capacity explains why a node pool isn't scaling down",
103
+ 'Nodes being removed show as removed, not failed',
104
+ 'Helm actions on Flux releases change the right objects',
105
+ 'Flux Sync with source matches flux reconcile --with-source',
106
+ 'Topology links Services only to the Pods they select',
107
+ 'Namespaces stuck Terminating show what holds them',
108
+ 'Filters stay put when you switch kinds or sections',
109
+ 'Skip an audit check per resource with an annotation',
110
+ ],
111
+ },
50
112
  {
51
113
  version: 'v1.16.0',
52
114
  highlights: [
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
2
2
 
3
3
  import { gitOpsOwnerFromRelationships } from '@skyhook-io/k8s-ui'
4
4
  import type { Relationships } from '../../types'
5
- import { supportsBatchExecution } from './WorkloadView'
5
+ import { collectServingRefs, supportsBatchExecution } from './WorkloadView'
6
6
  import { findInheritedGitOpsLookupRef } from '../../hooks/useResolvedGitOpsOwner'
7
7
 
8
8
  describe('findInheritedGitOpsLookupRef', () => {
@@ -75,3 +75,12 @@ describe('supportsBatchExecution', () => {
75
75
  expect(supportsBatchExecution('CronJob', 'cronjobs', 'batch')).toBe(true)
76
76
  })
77
77
  })
78
+
79
+
80
+ describe('collectServingRefs', () => {
81
+ it('loads moved upstream resources while retaining exact API groups', () => {
82
+ const broker = { kind: 'Broker', group: 'eventing.knative.dev', namespace: 'demo', name: 'messages' }
83
+ const other = { ...broker, group: 'other.example' }
84
+ expect(collectServingRefs({ routedFrom: [broker, broker, other] })).toEqual([broker, other])
85
+ })
86
+ })
@@ -1,8 +1,4 @@
1
- import { RayJobRenderer } from '../resources/renderers/RayJobRenderer'
2
- import { JobRenderer, JobSetRenderer } from '../resources/renderers/JobAdmissionRenderers'
3
- import { RayClusterRenderer } from '../resources/renderers/RayClusterRenderer'
4
- import { RayServiceRenderer } from '../resources/renderers/RayServiceRenderer'
5
- import { KueueWorkloadRenderer } from '../resources/renderers/KueueWorkloadRenderer'
1
+ import { decorateResourceDiagnose, resourceDetailRedirect, resourceDetailSlots, resourceLogs, resourceRendererOverrides } from '../../integrations/resourceHosts'
6
2
  import { useMemo, useEffect, useCallback, useRef, useState, type ReactNode } from 'react'
7
3
  import { useQueries, useQueryClient } from '@tanstack/react-query'
8
4
  import { Navigate, useNavigate, useLocation, useSearchParams } from 'react-router-dom'
@@ -11,13 +7,11 @@ import { clsx } from 'clsx'
11
7
  import { Terminal, Stethoscope } from 'lucide-react'
12
8
  import {
13
9
  WorkloadView as BaseWorkloadView,
14
- isApiGroup,
15
10
  EditableYamlView,
16
11
  FetchResult,
17
12
  Section,
18
13
  type WorkloadTabType,
19
14
  type WorkloadExtraTab,
20
- type RendererOverrides,
21
15
  type GitOpsOwnerRef,
22
16
  type HelmOwnerRef,
23
17
  type AppRow,
@@ -104,7 +98,7 @@ import { WorkloadCostTab } from '../cost/WorkloadCostTab'
104
98
  import { isOpenCostWorkloadKind } from '../cost/kinds'
105
99
  import { isRadarFeatureUnsupported } from '../../api/radarFeatures'
106
100
  import { useTrafficAvailable } from '../../api/traffic'
107
- import { useResourceAudit, useResourceIssues, useResources, useTrace, fetchTraceWithProbes, fetchInClusterCapability, runInClusterMerged } from '../../api/client'
101
+ import { useResourceAudit, useResourceIssues, useTrace, fetchTraceWithProbes, fetchInClusterCapability, runInClusterMerged } from '../../api/client'
108
102
  import { AuditAlerts, getRadarUpgradeRequirement, ResourceIssuesSection, ReachabilityView, TraceSummary, InClusterConsentDialog, traceFingerprint, staticPollUnreliable, summarizeInClusterTests, type Trace as NetworkTrace, type InClusterCapability, inClusterConsentGiven, consentRequestRows } from '@skyhook-io/k8s-ui'
109
103
  import { WorkloadLogsViewer } from '../logs/WorkloadLogsViewer'
110
104
  import { ScheduledWorkloadLogsViewer } from '../logs/ScheduledWorkloadLogsViewer'
@@ -128,38 +122,7 @@ import {
128
122
  defaultPathForPort,
129
123
  } from '../curl/ServiceCurlButton'
130
124
  import { useToast } from '../ui/Toast'
131
- import { Tooltip } from '../ui/Tooltip'
132
- import { PodRenderer } from '../resources/renderers/PodRenderer'
133
- import { KarpenterNodePoolRenderer } from '../resources/renderers/KarpenterNodePoolRenderer'
134
- import { NodeRenderer } from '../resources/renderers/NodeRenderer'
135
- import { ServiceRenderer } from '../resources/renderers/ServiceRenderer'
136
- import { WorkloadRenderer } from '../resources/renderers/WorkloadRenderer'
137
- import { CompositeRenderer } from '../resources/CompositeRenderer'
138
- import { ServiceAccountRenderer } from '../resources/renderers/ServiceAccountRenderer'
139
- import { RoleRenderer } from '../resources/renderers/RoleRenderer'
140
- import { RoleBindingRenderer } from '../resources/renderers/RoleBindingRenderer'
141
- import { NamespaceRenderer } from '../resources/renderers/NamespaceRenderer'
142
- import { CAPIClusterRenderer } from '../resources/renderers/CAPIClusterRenderer'
143
- import { HPARenderer } from '../resources/renderers/HPARenderer'
144
- import { PVCRenderer } from '../resources/renderers/PVCRenderer'
145
- import { RolloutRenderer } from '../resources/renderers/RolloutRenderer'
146
- import { KyvernoPolicyCoverage } from '../resources/renderers/KyvernoPolicyCoverage'
147
- import { KyvernoPolicyQueued } from '../resources/renderers/KyvernoPolicyQueued'
148
- import { CNPGObjectStoreRenderer } from '../resources/renderers/CNPGObjectStoreRenderer'
149
- import { VeleroBSLRenderer } from '../resources/renderers/VeleroBSLRenderer'
150
- import { VeleroBackupRenderer } from '../resources/renderers/VeleroBackupRenderer'
151
- import { VeleroRestoreRenderer } from '../resources/renderers/VeleroRestoreRenderer'
152
- import { CNPGClusterRenderer } from '../resources/renderers/CNPGClusterRenderer'
153
- import { CNPGImageCatalogRenderer } from '../resources/renderers/CNPGImageCatalogRenderer'
154
- import {
155
- CNPGDatabaseRenderer,
156
- CNPGPublicationRenderer,
157
- CNPGSubscriptionRenderer,
158
- } from '../resources/renderers/CNPGDeclarativeRenderer'
159
125
  import { CreateResourceDialog } from '../shared/CreateResourceDialog'
160
- import { renderCNPGSummary } from '../cnpg/CNPGSummaryHost'
161
- import { CNPGClusterLogs } from '../cnpg/CNPGClusterLogs'
162
- import { cnpgDetailKindFor, cnpgDetailPath } from '../cnpg/routes'
163
126
  import { cleanYamlForDuplicate } from '../../utils/skeleton-yaml'
164
127
  import { useDesktopDownload } from '../../hooks/useDesktopDownload'
165
128
  import { useCompareLauncher } from '../compare/useCompareLauncher'
@@ -186,41 +149,6 @@ export function supportsBatchExecution(kind: string, apiKind: string, group?: st
186
149
  return true
187
150
  }
188
151
 
189
- // Stable reference — web renderer wrappers inject platform hooks internally
190
- const rendererOverrides: RendererOverrides = {
191
- RayJobRenderer,
192
- JobRenderer,
193
- JobSetRenderer,
194
- RayServiceRenderer,
195
- RayClusterRenderer,
196
- KueueWorkloadRenderer,
197
- CAPIClusterRenderer,
198
- PodRenderer,
199
- KarpenterNodePoolRenderer,
200
- NodeRenderer,
201
- ServiceRenderer,
202
- WorkloadRenderer,
203
- CompositeRenderer,
204
- ServiceAccountRenderer,
205
- RoleRenderer,
206
- RoleBindingRenderer,
207
- NamespaceRenderer,
208
- HPARenderer,
209
- PVCRenderer,
210
- RolloutRenderer,
211
- KyvernoPolicyCoverage,
212
- KyvernoPolicyQueued,
213
- CNPGObjectStoreRenderer,
214
- VeleroBSLRenderer,
215
- VeleroBackupRenderer,
216
- VeleroRestoreRenderer,
217
- CNPGClusterRenderer,
218
- CNPGDatabaseRenderer,
219
- CNPGPublicationRenderer,
220
- CNPGSubscriptionRenderer,
221
- CNPGImageCatalogRenderer,
222
- }
223
-
224
152
  // ============================================================================
225
153
  // ROUTE WRAPPER — parses kind/ns/name from URL
226
154
  // ============================================================================
@@ -289,16 +217,8 @@ export function WorkloadViewRoute({ onNavigateToResource }: WorkloadViewRoutePro
289
217
  )
290
218
  }
291
219
 
292
- const cnpgPlural = cnpgDetailKindFor(kind, group)
293
- if (cnpgPlural) {
294
- const params = new URLSearchParams(searchParams)
295
- params.delete('apiGroup')
296
- const tab = params.get('tab')
297
- if (cnpgPlural === 'clusters' && (tab === 'timeline' || tab === 'events')) params.set('tab', 'activity')
298
- const base = cnpgDetailPath({ plural: cnpgPlural, namespace, name })
299
- const qs = params.toString()
300
- return <Navigate replace to={qs ? `${base}?${qs}` : base} state={location.state} />
301
- }
220
+ const detailPath = resourceDetailRedirect({ kind, group, namespace, name }, searchParams)
221
+ if (detailPath) return <Navigate replace to={detailPath} state={location.state} />
302
222
 
303
223
  return (
304
224
  <WorkloadView
@@ -341,6 +261,16 @@ interface WorkloadViewProps {
341
261
  pushTabHistory?: boolean
342
262
  breadcrumb?: ReactNode
343
263
  extraTabs?: WorkloadExtraTab[]
264
+ tabOrder?: string[]
265
+ subheader?: ReactNode
266
+ specTab?: { label?: string; icon?: ReactNode; lead?: ReactNode; render?: () => ReactNode }
267
+ titlePrefix?: ReactNode
268
+ inlineBadges?: boolean
269
+ namespaceNote?: ReactNode
270
+ renderStatusBadge?: (resource: any) => ReactNode
271
+ statusNote?: ReactNode
272
+ onRefresh?: () => Promise<unknown>
273
+ hideKindBadge?: boolean
344
274
  }
345
275
 
346
276
  interface ImageTargetOwnershipContext {
@@ -463,7 +393,7 @@ function useActionsBarProps(
463
393
  name: string
464
394
  className?: string
465
395
  }) => <PortForwardButton type={type} namespace={ns} name={n} className={className} />,
466
- renderDiagnose,
396
+ renderDiagnose: decorateResourceDiagnose(renderDiagnose),
467
397
  onDelete: (
468
398
  params: Parameters<typeof deleteMutation.mutate>[0],
469
399
  callbacks?: { onSuccess?: () => void },
@@ -471,7 +401,8 @@ function useActionsBarProps(
471
401
  isDeleting: deleteMutation.isPending,
472
402
  cascadeDependents: cascadePreview?.dependents,
473
403
  cascadeLoading,
474
- cascadeRootResolved: cascadeError ? false : cascadePreview?.rootResolved,
404
+ cascadeRootResolved: cascadePreview?.rootResolved,
405
+ cascadePreviewFailed: Boolean(cascadeError),
475
406
  onRestart: rolloutAllows('restart')
476
407
  ? (params: Parameters<typeof restartWorkloadMutation.mutate>[0]) =>
477
408
  restartWorkloadMutation.mutate(params)
@@ -645,9 +576,11 @@ export function WorkloadView({
645
576
  () => (isDiagnoseKind(apiKind, effectiveGroup) ? [] : (relationships?.services ?? [])),
646
577
  [apiKind, effectiveGroup, relationships],
647
578
  )
579
+ const onRefresh = rest.onRefresh
648
580
  const refetchResourceAndRuns = useCallback(async () => {
649
581
  await Promise.all([
650
582
  refetchResource(),
583
+ onRefresh?.(),
651
584
  ...(apiKind === 'rayjobs' && effectiveGroup === 'ray.io' ? [
652
585
  queryClient.refetchQueries({ queryKey: ['resource', 'jobs', namespace, name, 'batch'], type: 'active' }),
653
586
  ...(resource?.spec?.clusterSelector?.['ray.io/cluster'] ? [queryClient.refetchQueries({ queryKey: ['resource', 'rayclusters', namespace, resource.spec.clusterSelector['ray.io/cluster'], 'ray.io'], type: 'active' })] : []),
@@ -661,7 +594,7 @@ export function WorkloadView({
661
594
  ...(apiKind === 'rayclusters' ? { type: 'active' as const } : {}),
662
595
  }),
663
596
  ])
664
- }, [apiKind, effectiveGroup, name, namespace, queryClient, refetchResource, resource?.spec?.clusterSelector])
597
+ }, [apiKind, effectiveGroup, name, namespace, queryClient, refetchResource, resource?.spec?.clusterSelector, onRefresh])
665
598
  const podWorkloadOwner = useMemo(
666
599
  () => podWorkloadOwnerFromRelationships(apiKind, namespace, relationships, resource),
667
600
  [apiKind, namespace, relationships, resource],
@@ -1249,9 +1182,7 @@ export function WorkloadView({
1249
1182
  onSelectRun={handleSelectedRunChange}
1250
1183
  />
1251
1184
  )}
1252
- renderSummary={({ apiKind: ak, namespace: ns, name: n, resource: res, context, onNavigate }) =>
1253
- renderCNPGSummary({ apiKind: ak, namespace: ns, name: n, group: effectiveGroup, resource: res, context, onNavigate })
1254
- }
1185
+ {...resourceDetailSlots({ kind: apiKind, group: resource?.apiVersion ? resourceGroup : effectiveGroup, namespace, name }, rest.extraTabs)}
1255
1186
  renderExpandedOverview={({ kind: k, apiKind, namespace: ns, name: n, resource: res }) =>
1256
1187
  supportsBatchExecution(k, apiKind, effectiveGroup, res?.apiVersion) &&
1257
1188
  res ? (
@@ -1316,7 +1247,7 @@ export function WorkloadView({
1316
1247
  onDuplicate={handleDuplicate}
1317
1248
  onDownload={desktopDownload}
1318
1249
  actionsBarProps={actionsBarProps}
1319
- rendererOverrides={rendererOverrides}
1250
+ rendererOverrides={resourceRendererOverrides}
1320
1251
  renderOverviewExtra={({ kind: k, namespace: ns, name: n, group: g, context }) => {
1321
1252
  // Network entry kinds (Service/Ingress/Route/Gateway) ARE the diagnosis
1322
1253
  // target: DiagnoseInlineSection renders in the drawer, no hint. Workload
@@ -1334,7 +1265,6 @@ export function WorkloadView({
1334
1265
  <>
1335
1266
  {diagnoseInline}
1336
1267
  {diagnoseHint}
1337
- <FluxSourceConsumersSection kind={k} namespace={ns} name={n} />
1338
1268
  <AuditOverviewSection
1339
1269
  findings={auditFindings ?? []}
1340
1270
  onViewAll={() => navigateRouter('/checks')}
@@ -1345,6 +1275,7 @@ export function WorkloadView({
1345
1275
  renderOverviewLead={() => (
1346
1276
  <ResourceIssuesSection
1347
1277
  issues={liveIssues}
1278
+ compact={!expanded}
1348
1279
  subjectResource={{ kind: apiKind, namespace, name, group: rest.group }}
1349
1280
  onResourceClick={
1350
1281
  rest.onNavigateToResource
@@ -1393,13 +1324,14 @@ export function WorkloadView({
1393
1324
  )
1394
1325
  }
1395
1326
 
1396
- function collectServingRefs(relationships: Relationships | undefined): ResourceRef[] {
1327
+ export function collectServingRefs(relationships: Relationships | undefined): ResourceRef[] {
1397
1328
  if (!relationships) return []
1398
1329
  return dedupeResourceRefs([
1399
1330
  ...(relationships.services ?? []),
1400
1331
  ...(relationships.ingresses ?? []),
1401
1332
  ...(relationships.gateways ?? []),
1402
1333
  ...(relationships.routes ?? []),
1334
+ ...(relationships.routedFrom ?? []),
1403
1335
  ])
1404
1336
  }
1405
1337
 
@@ -1539,9 +1471,8 @@ function LogsTabContent({
1539
1471
  )
1540
1472
  }
1541
1473
 
1542
- if (kind === 'Cluster' && isApiGroup(resource?.apiVersion, 'postgresql.cnpg.io')) {
1543
- return <CNPGClusterLogs namespace={namespace} name={name} />
1544
- }
1474
+ const integrationLogs = resourceLogs({ kind, group, namespace, name }, resource)
1475
+ if (integrationLogs) return integrationLogs
1545
1476
 
1546
1477
  // Workload kinds with stable pod selectors use the aggregated workload logs viewer
1547
1478
  if (WORKLOAD_LOG_KINDS.has(kind) && (kind !== 'Job' || isCoreBatchJob(apiKind, group))) {
@@ -2217,137 +2148,6 @@ function DiagnoseInlineSection({ kind, namespace, name, group, onOpenReachabilit
2217
2148
  )
2218
2149
  }
2219
2150
 
2220
- // FluxSourceConsumersSection lists the reconcilers (Kustomization, HelmRelease)
2221
- // that reference this Flux source CR — the inverse of `spec.sourceRef`. Renders
2222
- // only when the focused resource is a Flux source kind; otherwise null. Sources
2223
- // can have many consumers (one repo feeding multiple apps), so this answers
2224
- // "if I edit this source, what gets affected on the next reconcile?".
2225
- //
2226
- // Filtering happens client-side off the namespaced reconciler lists — these
2227
- // are typically small (tens, not thousands) and the dynamic informer cache
2228
- // makes the request cheap. If a cluster ever has thousands of HelmReleases,
2229
- // a dedicated /api/gitops/consumers endpoint would be the right move; today
2230
- // it'd be premature.
2231
- // Outer component is cheap — it does only the kind check and decides whether
2232
- // to mount the data-fetching child. Without this split, useResources would
2233
- // fire two API calls on EVERY workload drawer open (Pod, Deployment, Service,
2234
- // …), since the hook has no `enabled` flag and can't be conditionally called
2235
- // (Rules of Hooks). The hooks only need to run when the focused resource is
2236
- // actually a Flux source CR.
2237
- function FluxSourceConsumersSection({
2238
- kind,
2239
- namespace,
2240
- name,
2241
- }: {
2242
- kind: string
2243
- namespace: string
2244
- name: string
2245
- }) {
2246
- // The inner WorkloadView de-pluralizes the URL's plural form, which gives
2247
- // "Gitrepository" (single-uppercase) rather than the wire-correct
2248
- // "GitRepository" — so we match lowercase. spec.sourceRef.kind on consumers
2249
- // is always wire-correct, so we look that up separately.
2250
- const sourceKind = FLUX_SOURCE_KIND_BY_LOWER.get(kind.toLowerCase()) ?? null
2251
- if (!sourceKind) return null
2252
- return <FluxSourceConsumersInner sourceKind={sourceKind} namespace={namespace} name={name} />
2253
- }
2254
-
2255
- function FluxSourceConsumersInner({
2256
- sourceKind,
2257
- namespace,
2258
- name,
2259
- }: {
2260
- sourceKind: string
2261
- namespace: string
2262
- name: string
2263
- }) {
2264
- const navigate = useNavigate()
2265
- const { data: kustomizations } = useResources<any>(
2266
- 'kustomizations',
2267
- undefined,
2268
- 'kustomize.toolkit.fluxcd.io',
2269
- )
2270
- const { data: helmReleases } = useResources<any>(
2271
- 'helmreleases',
2272
- undefined,
2273
- 'helm.toolkit.fluxcd.io',
2274
- )
2275
-
2276
- const consumers: Array<{
2277
- kind: 'Kustomization' | 'HelmRelease'
2278
- namespace: string
2279
- name: string
2280
- plural: string
2281
- }> = []
2282
- for (const k of kustomizations ?? []) {
2283
- const ref = k?.spec?.sourceRef ?? {}
2284
- const refNs = ref.namespace || k?.metadata?.namespace
2285
- if (ref.kind === sourceKind && ref.name === name && refNs === namespace) {
2286
- consumers.push({
2287
- kind: 'Kustomization',
2288
- namespace: k.metadata.namespace,
2289
- name: k.metadata.name,
2290
- plural: 'kustomizations',
2291
- })
2292
- }
2293
- }
2294
- for (const h of helmReleases ?? []) {
2295
- const ref = h?.spec?.chart?.spec?.sourceRef ?? {}
2296
- const refNs = ref.namespace || h?.metadata?.namespace
2297
- if (ref.kind === sourceKind && ref.name === name && refNs === namespace) {
2298
- consumers.push({
2299
- kind: 'HelmRelease',
2300
- namespace: h.metadata.namespace,
2301
- name: h.metadata.name,
2302
- plural: 'helmreleases',
2303
- })
2304
- }
2305
- }
2306
-
2307
- if (consumers.length === 0) {
2308
- return (
2309
- <section className="rounded-lg border border-theme-border bg-theme-surface p-4 shadow-theme-sm">
2310
- <h3 className="mb-2 text-sm font-semibold text-theme-text-primary">Consumed by</h3>
2311
- <p className="text-xs text-theme-text-tertiary">
2312
- No Kustomization or HelmRelease references this source.
2313
- </p>
2314
- </section>
2315
- )
2316
- }
2317
-
2318
- return (
2319
- <section className="rounded-lg border border-theme-border bg-theme-surface p-4 shadow-theme-sm">
2320
- <h3 className="mb-3 text-sm font-semibold text-theme-text-primary">
2321
- Consumed by ({consumers.length})
2322
- </h3>
2323
- <div className="flex flex-wrap gap-1.5">
2324
- {consumers.map((c) => (
2325
- <Tooltip
2326
- key={`${c.kind}/${c.namespace}/${c.name}`}
2327
- content={`${c.kind} ${c.namespace}/${c.name}`}
2328
- >
2329
- <button
2330
- onClick={() =>
2331
- navigate(
2332
- `/gitops/detail/${c.plural}/${encodeURIComponent(c.namespace)}/${encodeURIComponent(c.name)}`,
2333
- )
2334
- }
2335
- className="inline-flex items-center gap-1.5 rounded border border-theme-border bg-theme-surface px-1.5 py-0.5 text-[11px] text-theme-text-secondary hover:border-skyhook-500/60 hover:text-skyhook-500 transition-colors"
2336
- >
2337
- <span className="text-theme-text-tertiary">
2338
- {c.kind === 'HelmRelease' ? 'HR' : 'K'}
2339
- </span>
2340
- <span>
2341
- {c.namespace}/{c.name}
2342
- </span>
2343
- </button>
2344
- </Tooltip>
2345
- ))}
2346
- </div>
2347
- </section>
2348
- )
2349
- }
2350
-
2351
2151
  // Drawer mode: single chart + category tabs (compact for ~500px width).
2352
2152
  // Full-screen mode: multi-chart grid so CPU + Memory + Network can be
2353
2153
  // compared side-by-side without tab switching.
@@ -2422,18 +2222,6 @@ function DrawerMetricsContent({
2422
2222
  )
2423
2223
  }
2424
2224
 
2425
- // FLUX_SOURCE_KIND_BY_LOWER maps lowercase kind (what the inner WorkloadView
2426
- // produces via its plural-to-singular fallback) to the wire-correct
2427
- // PascalCase form that consumers carry in spec.sourceRef.kind. HelmChart is
2428
- // intentionally absent — it's an auto-generated internal CR, not something
2429
- // users create or point reconcilers at directly.
2430
- const FLUX_SOURCE_KIND_BY_LOWER = new Map<string, string>([
2431
- ['gitrepository', 'GitRepository'],
2432
- ['helmrepository', 'HelmRepository'],
2433
- ['ocirepository', 'OCIRepository'],
2434
- ['bucket', 'Bucket'],
2435
- ])
2436
-
2437
2225
  // Read-only manifest view for an object in the workload's neighborhood (the
2438
2226
  // YAML tab's object rail). Read-only by design — editing an arbitrary related
2439
2227
  // object belongs on that resource's own page.
@@ -0,0 +1,29 @@
1
+ // @vitest-environment jsdom
2
+ import { act } from 'react'
3
+ import { createRoot } from 'react-dom/client'
4
+ import { expect, it, vi } from 'vitest'
5
+ import { Mono } from './layout'
6
+
7
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
8
+
9
+ it('uses normal word boundaries with overflow wrapping and keeps the complete value in a hover tooltip', () => {
10
+ vi.useFakeTimers()
11
+ const host = document.createElement('div')
12
+ document.body.append(host)
13
+ const root = createRoot(host)
14
+ const image = 'ghcr.io/cloudnative-pg/postgresql:17.4-standard-bookworm'
15
+ act(() => root.render(<Mono>{image}</Mono>))
16
+ const mono = host.querySelector('.font-mono')!
17
+ expect(mono.classList.contains('break-all')).toBe(false)
18
+ expect(mono.classList.contains('break-normal')).toBe(true)
19
+ expect(mono.textContent).toBe(image)
20
+ expect(mono.classList.contains('[overflow-wrap:anywhere]')).toBe(true)
21
+ const trigger = mono.parentElement!
22
+ act(() => trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })))
23
+ act(() => trigger.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })))
24
+ act(() => vi.advanceTimersByTime(500))
25
+ expect(document.body.querySelector('.fixed')?.textContent).toBe(image)
26
+ act(() => root.unmount())
27
+ host.remove()
28
+ vi.useRealTimers()
29
+ })