@skyhook-io/radar-app 1.16.3 → 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 (321) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/App.tsx +138 -93
  4. package/src/RadarApp.tsx +27 -17
  5. package/src/api/actions.errorCode.test.ts +40 -0
  6. package/src/api/actions.test.ts +48 -0
  7. package/src/api/actions.ts +79 -0
  8. package/src/api/apiResources.navigation.test.tsx +232 -0
  9. package/src/api/apiResources.ts +46 -3
  10. package/src/api/client.goneResource.test.ts +49 -0
  11. package/src/api/client.helmSource.test.ts +39 -0
  12. package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
  13. package/src/api/client.namespaceSwitch.test.ts +107 -0
  14. package/src/api/client.ts +381 -47
  15. package/src/api/cnpg-ha.live.test.ts +140 -0
  16. package/src/api/cnpg-ha.test.ts +78 -0
  17. package/src/api/cnpg-ha.ts +19 -0
  18. package/src/api/cnpg-history.ts +123 -0
  19. package/src/api/cnpg-inspect.ts +102 -0
  20. package/src/api/cnpg-protection.ts +112 -0
  21. package/src/api/cnpg-recovery.test.tsx +60 -0
  22. package/src/api/cnpg-recovery.ts +262 -0
  23. package/src/api/cnpg-sessions.ts +175 -0
  24. package/src/api/cnpg-storage.ts +138 -0
  25. package/src/api/cnpg.legacy-reads.test.ts +43 -0
  26. package/src/api/cnpg.pooler-runtime.test.ts +15 -0
  27. package/src/api/cnpg.ts +428 -10
  28. package/src/api/diagnose.test.ts +40 -0
  29. package/src/api/diagnose.ts +12 -1
  30. package/src/api/radarFeatures.render.test.tsx +65 -3
  31. package/src/api/radarFeatures.ts +6 -0
  32. package/src/api/reviewedActionMutation.test.tsx +164 -0
  33. package/src/api/reviewedActionMutation.ts +66 -0
  34. package/src/api/timelineSource.test.ts +9 -0
  35. package/src/api/timelineSource.ts +8 -0
  36. package/src/api/traffic.sources.test.ts +55 -0
  37. package/src/api/traffic.ts +173 -14
  38. package/src/components/applications/ApplicationsView.tsx +3 -0
  39. package/src/components/audit/AuditView.tsx +9 -0
  40. package/src/components/capacity/CapacityActivity.tsx +3 -31
  41. package/src/components/capacity/CapacityDemand.tsx +4 -36
  42. package/src/components/capacity/CapacityOverview.tsx +217 -56
  43. package/src/components/capacity/CapacityPoolDetail.tsx +24 -62
  44. package/src/components/capacity/CapacityView.test.tsx +354 -1
  45. package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
  46. package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
  47. package/src/components/capacity/routes.ts +42 -0
  48. package/src/components/capacity/shared.tsx +23 -150
  49. package/src/components/capacity/workspaceRoute.ts +18 -0
  50. package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
  51. package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
  52. package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
  53. package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
  54. package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
  55. package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
  56. package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
  57. package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
  58. package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
  59. package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
  60. package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
  61. package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
  62. package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
  63. package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
  64. package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
  65. package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
  66. package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
  67. package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
  68. package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
  69. package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
  70. package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
  71. package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
  72. package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
  73. package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
  74. package/src/components/cnpg/CNPGOperator.tsx +78 -30
  75. package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
  76. package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
  77. package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
  78. package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
  79. package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
  80. package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
  81. package/src/components/cnpg/CNPGOverview.tsx +238 -63
  82. package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
  83. package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
  84. package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
  85. package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
  86. package/src/components/cnpg/CNPGPerformance.tsx +477 -0
  87. package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
  88. package/src/components/cnpg/CNPGPooling.tsx +97 -40
  89. package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
  90. package/src/components/cnpg/CNPGProtection.tsx +230 -124
  91. package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
  92. package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
  93. package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
  94. package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
  95. package/src/components/cnpg/CNPGStorage.tsx +619 -0
  96. package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
  97. package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
  98. package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
  99. package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
  100. package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
  101. package/src/components/cnpg/CNPGTrends.test.ts +38 -0
  102. package/src/components/cnpg/CNPGTrends.tsx +702 -0
  103. package/src/components/cnpg/CNPGView.tsx +34 -21
  104. package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
  105. package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
  106. package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
  107. package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
  108. package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
  109. package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
  110. package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
  111. package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
  112. package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
  113. package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
  114. package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
  115. package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
  116. package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
  117. package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
  118. package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
  119. package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
  120. package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
  121. package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
  122. package/src/components/cnpg/actions/actionModel.test.ts +111 -0
  123. package/src/components/cnpg/actions/actionModel.ts +127 -0
  124. package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
  125. package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
  126. package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
  127. package/src/components/cnpg/archivingRepair.test.ts +104 -0
  128. package/src/components/cnpg/archivingRepair.ts +145 -0
  129. package/src/components/cnpg/baseBackup.test.ts +36 -0
  130. package/src/components/cnpg/baseBackup.ts +36 -0
  131. package/src/components/cnpg/blocking.test.ts +59 -0
  132. package/src/components/cnpg/blocking.ts +131 -0
  133. package/src/components/cnpg/coverageText.test.ts +27 -0
  134. package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
  135. package/src/components/cnpg/creation/targetModel.test.ts +137 -0
  136. package/src/components/cnpg/creation/targetModel.ts +149 -0
  137. package/src/components/cnpg/fleetStatus.test.ts +46 -0
  138. package/src/components/cnpg/fleetStatus.ts +43 -0
  139. package/src/components/cnpg/formFields.ts +2 -0
  140. package/src/components/cnpg/grantText.test.tsx +11 -0
  141. package/src/components/cnpg/host.test.tsx +24 -0
  142. package/src/components/cnpg/host.tsx +101 -0
  143. package/src/components/cnpg/inspectModel.test.ts +120 -0
  144. package/src/components/cnpg/inspectModel.ts +140 -0
  145. package/src/components/cnpg/logicalSlots.test.ts +30 -0
  146. package/src/components/cnpg/logicalSlots.ts +29 -0
  147. package/src/components/cnpg/lsn.test.ts +40 -0
  148. package/src/components/cnpg/lsn.ts +43 -0
  149. package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
  150. package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
  151. package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
  152. package/src/components/cnpg/operations/model.test.ts +510 -0
  153. package/src/components/cnpg/operations/model.ts +499 -0
  154. package/src/components/cnpg/operations/presentation.test.ts +80 -0
  155. package/src/components/cnpg/operations/presentation.ts +84 -0
  156. package/src/components/cnpg/operations/store.ts +98 -0
  157. package/src/components/cnpg/operatorStatus.test.ts +134 -0
  158. package/src/components/cnpg/operatorStatus.ts +194 -0
  159. package/src/components/cnpg/paths.test.ts +54 -0
  160. package/src/components/cnpg/paths.ts +47 -4
  161. package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
  162. package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
  163. package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
  164. package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
  165. package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
  166. package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
  167. package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
  168. package/src/components/cnpg/protection/scheduleModel.ts +16 -0
  169. package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
  170. package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
  171. package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
  172. package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
  173. package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
  174. package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
  175. package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
  176. package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
  177. package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
  178. package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
  179. package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
  180. package/src/components/cnpg/recovery/restoreModel.ts +685 -0
  181. package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
  182. package/src/components/cnpg/refresh.test.ts +38 -0
  183. package/src/components/cnpg/refresh.ts +17 -0
  184. package/src/components/cnpg/refreshNotice.test.tsx +29 -0
  185. package/src/components/cnpg/routes.test.ts +34 -3
  186. package/src/components/cnpg/routes.ts +60 -48
  187. package/src/components/cnpg/runtimeAssessment.ts +198 -0
  188. package/src/components/cnpg/runtimeModel.test.ts +175 -0
  189. package/src/components/cnpg/runtimeModel.ts +196 -0
  190. package/src/components/cnpg/runtimeParts.test.tsx +17 -0
  191. package/src/components/cnpg/runtimeParts.tsx +79 -0
  192. package/src/components/cnpg/shared.test.tsx +59 -0
  193. package/src/components/cnpg/shared.tsx +100 -182
  194. package/src/components/cnpg/storageModel.test.ts +107 -0
  195. package/src/components/cnpg/storageModel.ts +101 -0
  196. package/src/components/cnpg/trendSamples.test.ts +148 -0
  197. package/src/components/cnpg/trendSamples.ts +222 -0
  198. package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
  199. package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
  200. package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
  201. package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
  202. package/src/components/cnpg/useCNPGNavigate.ts +21 -0
  203. package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
  204. package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
  205. package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
  206. package/src/components/cnpg/workspaceRoute.ts +23 -0
  207. package/src/components/cost/ApplicationCostTab.test.ts +7 -0
  208. package/src/components/cost/ApplicationCostTab.tsx +7 -7
  209. package/src/components/cost/CostView.test.ts +2 -1
  210. package/src/components/cost/CostView.tsx +0 -2
  211. package/src/components/cost/WorkloadCostTab.tsx +2 -4
  212. package/src/components/cost/errors.ts +3 -1
  213. package/src/components/cost/source.test.ts +10 -0
  214. package/src/components/cost/source.ts +5 -1
  215. package/src/components/diagnose/AgentSetupNotice.test.tsx +58 -0
  216. package/src/components/diagnose/AgentSetupNotice.tsx +142 -51
  217. package/src/components/diagnose/ApplyDialog.ack.test.tsx +47 -0
  218. package/src/components/diagnose/ApplyDialog.test.tsx +59 -6
  219. package/src/components/diagnose/ApplyDialog.tsx +26 -28
  220. package/src/components/diagnose/DiagnoseContext.test.ts +43 -0
  221. package/src/components/diagnose/DiagnoseContext.tsx +136 -54
  222. package/src/components/diagnose/DiagnoseSurface.tsx +16 -3
  223. package/src/components/diagnose/InvestigationView.tsx +39 -2
  224. package/src/components/diagnose/LocalDiagnoseAction.tsx +40 -20
  225. package/src/components/diagnose/agentCatalog.ts +2 -2
  226. package/src/components/diagnose/diagnoseEvidenceTypes.ts +2 -0
  227. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
  228. package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
  229. package/src/components/dock/BottomDock.tsx +2 -0
  230. package/src/components/dock/TerminalTab.test.tsx +17 -0
  231. package/src/components/dock/TerminalTab.tsx +7 -3
  232. package/src/components/dock/TrafficFlowListTab.tsx +27 -6
  233. package/src/components/execution/BatchExecutionView.tsx +11 -11
  234. package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
  235. package/src/components/gitops/GitOpsView.tsx +16 -23
  236. package/src/components/helm/ChartBrowser.tsx +3 -3
  237. package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
  238. package/src/components/helm/HelmCompareRoute.tsx +57 -26
  239. package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
  240. package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
  241. package/src/components/helm/RevisionHistory.tsx +3 -2
  242. package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
  243. package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
  244. package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
  245. package/src/components/helm/ValuesViewer.test.tsx +301 -0
  246. package/src/components/helm/ValuesViewer.tsx +169 -78
  247. package/src/components/home/ActivitySummary.tsx +2 -1
  248. package/src/components/home/ClusterHealthCard.tsx +7 -0
  249. package/src/components/home/HomeView.tsx +20 -9
  250. package/src/components/home/ProblemsPanel.test.tsx +81 -0
  251. package/src/components/home/TopologyPreview.tsx +2 -2
  252. package/src/components/home/mcpToolCatalog.ts +1 -1
  253. package/src/components/issues/IssuesPane.render.test.tsx +122 -0
  254. package/src/components/issues/IssuesPane.test.ts +65 -1
  255. package/src/components/issues/IssuesPane.tsx +188 -25
  256. package/src/components/issues/uncheckedSources.ts +21 -0
  257. package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
  258. package/src/components/resource/PVCUsageBar.test.tsx +24 -0
  259. package/src/components/resource/PVCUsageBar.tsx +15 -6
  260. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
  261. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
  262. package/src/components/resources/CompositeRenderer.tsx +4 -4
  263. package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
  264. package/src/components/resources/ResourcesView.tsx +26 -12
  265. package/src/components/resources/renderers/KyvernoPolicyQueued.tsx +2 -1
  266. package/src/components/resources/renderers/NodeRenderer.tsx +7 -1
  267. package/src/components/resources/renderers/ServiceRenderer.test.tsx +81 -0
  268. package/src/components/resources/renderers/ServiceRenderer.tsx +19 -14
  269. package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
  270. package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
  271. package/src/components/settings/SettingsDialog.tsx +150 -34
  272. package/src/components/shared/LargeClusterNamespacePicker.tsx +11 -5
  273. package/src/components/timeline/TimelineList.tsx +25 -24
  274. package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
  275. package/src/components/timeline/TimelineLoadError.tsx +96 -0
  276. package/src/components/timeline/TimelineRetentionNote.test.tsx +49 -0
  277. package/src/components/timeline/TimelineRetentionNote.tsx +20 -0
  278. package/src/components/timeline/TimelineView.tsx +25 -45
  279. package/src/components/timeline/ViewModeToggle.tsx +20 -0
  280. package/src/components/timeline/useStickyError.test.tsx +47 -0
  281. package/src/components/timeline/useStickyError.ts +15 -0
  282. package/src/components/traffic/TrafficConnectionsTable.tsx +144 -0
  283. package/src/components/traffic/TrafficFilterSidebar.tsx +22 -1
  284. package/src/components/traffic/TrafficFlowListContext.tsx +12 -4
  285. package/src/components/traffic/TrafficFocusSearch.tsx +172 -0
  286. package/src/components/traffic/TrafficGraph.tsx +109 -26
  287. package/src/components/traffic/TrafficGraphTooLarge.tsx +150 -0
  288. package/src/components/traffic/TrafficView.tsx +582 -149
  289. package/src/components/traffic/trafficFilters.test.ts +398 -1
  290. package/src/components/traffic/trafficFilters.ts +408 -0
  291. package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
  292. package/src/components/ui/Markdown.test.tsx +65 -0
  293. package/src/components/ui/Markdown.tsx +144 -127
  294. package/src/components/whats-new/releaseNotes.ts +63 -1
  295. package/src/components/workload/WorkloadView.test.ts +11 -1
  296. package/src/components/workload/WorkloadView.tsx +162 -529
  297. package/src/components/workspace/layout.test.tsx +29 -0
  298. package/src/components/workspace/layout.tsx +259 -0
  299. package/src/components/workspace/table.ts +7 -0
  300. package/src/context/TimelineCustomization.tsx +15 -0
  301. package/src/contexts/CapabilitiesContext.tsx +1 -1
  302. package/src/hooks/useGitOpsWriteGuard.test.tsx +457 -0
  303. package/src/hooks/useGitOpsWriteGuard.ts +193 -0
  304. package/src/hooks/useResolvedGitOpsOwner.ts +256 -0
  305. package/src/integrations/resourceHost.test.tsx +55 -0
  306. package/src/integrations/resourceHost.ts +99 -0
  307. package/src/integrations/resourceHosts.test.tsx +141 -0
  308. package/src/integrations/resourceHosts.tsx +119 -0
  309. package/src/integrations/workspaceRoute.ts +20 -0
  310. package/src/integrations/workspaceRoutes.test.ts +40 -0
  311. package/src/integrations/workspaceRoutes.ts +22 -0
  312. package/src/integrations/workspaceScreens.tsx +14 -0
  313. package/src/utils/drawer-trail.ts +35 -0
  314. package/src/utils/kindFilterStore.test.ts +147 -0
  315. package/src/utils/kindFilterStore.ts +91 -0
  316. package/src/utils/navigation.test.ts +13 -1
  317. package/src/utils/navigation.ts +39 -2
  318. package/src/utils/page-links.ts +21 -0
  319. package/src/utils/tabStorage.ts +40 -0
  320. package/src/utils/viewMemory.test.ts +78 -0
  321. package/src/utils/viewMemory.ts +54 -0
@@ -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,34 +7,38 @@ 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
- type GitOpsStatus,
23
16
  type HelmOwnerRef,
24
17
  type AppRow,
25
18
  type ResourceOwnershipContext,
26
19
  type ServingResourceDetail,
27
20
  type AuditFinding,
21
+ dedupeResourceRefs,
28
22
  gitOpsRouteForOwner,
29
23
  gitOpsOwnerFromRelationships,
30
- getGitOpsResourceStatus,
31
24
  isDiagnoseKind,
32
25
  isRolloutKind,
33
26
  canSetWorkloadImages,
34
27
  isCoreBatchJob,
35
- type ManagedImageSource,
28
+ type SetImageOwnership,
29
+ SET_IMAGE_WRITES,
30
+ setImageWrites,
31
+ setImageSelectionKey,
32
+ gitOpsWriteEvidencePaths,
33
+ type SetImageSelection,
36
34
  type WorkloadImageTarget,
37
35
  } from '@skyhook-io/k8s-ui'
38
36
  import type { ServicePortRenderProps } from '@skyhook-io/k8s-ui/components/resources/renderers/ServiceRenderer'
39
37
  import { isJobSetV1Alpha2 } from '@skyhook-io/k8s-ui/components/resources/resource-utils-jobset-lws'
40
38
  import type { SelectedResource, ResourceRef, Relationships, ResourceWithRelationships } from '../../types'
41
39
  import { useHistoryPaging } from './historyPaging'
40
+ import { findInheritedGitOpsLookupRef, useResolvedGitOpsOwner } from '../../hooks/useResolvedGitOpsOwner'
41
+ import { useGitOpsWriteGuard } from '../../hooks/useGitOpsWriteGuard'
42
42
  import {
43
43
  kindToPlural,
44
44
  kindToPluralWithGroup,
@@ -97,7 +97,8 @@ import { RightsizingPanel } from '../resource/RightsizingStrip'
97
97
  import { WorkloadCostTab } from '../cost/WorkloadCostTab'
98
98
  import { isOpenCostWorkloadKind } from '../cost/kinds'
99
99
  import { isRadarFeatureUnsupported } from '../../api/radarFeatures'
100
- import { useResourceAudit, useResourceIssues, useResources, useTrace, fetchTraceWithProbes, fetchInClusterCapability, runInClusterMerged } from '../../api/client'
100
+ import { useTrafficAvailable } from '../../api/traffic'
101
+ import { useResourceAudit, useResourceIssues, useTrace, fetchTraceWithProbes, fetchInClusterCapability, runInClusterMerged } from '../../api/client'
101
102
  import { AuditAlerts, getRadarUpgradeRequirement, ResourceIssuesSection, ReachabilityView, TraceSummary, InClusterConsentDialog, traceFingerprint, staticPollUnreliable, summarizeInClusterTests, type Trace as NetworkTrace, type InClusterCapability, inClusterConsentGiven, consentRequestRows } from '@skyhook-io/k8s-ui'
102
103
  import { WorkloadLogsViewer } from '../logs/WorkloadLogsViewer'
103
104
  import { ScheduledWorkloadLogsViewer } from '../logs/ScheduledWorkloadLogsViewer'
@@ -121,38 +122,7 @@ import {
121
122
  defaultPathForPort,
122
123
  } from '../curl/ServiceCurlButton'
123
124
  import { useToast } from '../ui/Toast'
124
- import { Tooltip } from '../ui/Tooltip'
125
- import { PodRenderer } from '../resources/renderers/PodRenderer'
126
- import { KarpenterNodePoolRenderer } from '../resources/renderers/KarpenterNodePoolRenderer'
127
- import { NodeRenderer } from '../resources/renderers/NodeRenderer'
128
- import { ServiceRenderer } from '../resources/renderers/ServiceRenderer'
129
- import { WorkloadRenderer } from '../resources/renderers/WorkloadRenderer'
130
- import { CompositeRenderer } from '../resources/CompositeRenderer'
131
- import { ServiceAccountRenderer } from '../resources/renderers/ServiceAccountRenderer'
132
- import { RoleRenderer } from '../resources/renderers/RoleRenderer'
133
- import { RoleBindingRenderer } from '../resources/renderers/RoleBindingRenderer'
134
- import { NamespaceRenderer } from '../resources/renderers/NamespaceRenderer'
135
- import { CAPIClusterRenderer } from '../resources/renderers/CAPIClusterRenderer'
136
- import { HPARenderer } from '../resources/renderers/HPARenderer'
137
- import { PVCRenderer } from '../resources/renderers/PVCRenderer'
138
- import { RolloutRenderer } from '../resources/renderers/RolloutRenderer'
139
- import { KyvernoPolicyCoverage } from '../resources/renderers/KyvernoPolicyCoverage'
140
- import { KyvernoPolicyQueued } from '../resources/renderers/KyvernoPolicyQueued'
141
- import { CNPGObjectStoreRenderer } from '../resources/renderers/CNPGObjectStoreRenderer'
142
- import { VeleroBSLRenderer } from '../resources/renderers/VeleroBSLRenderer'
143
- import { VeleroBackupRenderer } from '../resources/renderers/VeleroBackupRenderer'
144
- import { VeleroRestoreRenderer } from '../resources/renderers/VeleroRestoreRenderer'
145
- import { CNPGClusterRenderer } from '../resources/renderers/CNPGClusterRenderer'
146
- import { CNPGImageCatalogRenderer } from '../resources/renderers/CNPGImageCatalogRenderer'
147
- import {
148
- CNPGDatabaseRenderer,
149
- CNPGPublicationRenderer,
150
- CNPGSubscriptionRenderer,
151
- } from '../resources/renderers/CNPGDeclarativeRenderer'
152
125
  import { CreateResourceDialog } from '../shared/CreateResourceDialog'
153
- import { renderCNPGSummary } from '../cnpg/CNPGSummaryHost'
154
- import { CNPGClusterLogs } from '../cnpg/CNPGClusterLogs'
155
- import { cnpgDetailKindFor, cnpgDetailPath } from '../cnpg/routes'
156
126
  import { cleanYamlForDuplicate } from '../../utils/skeleton-yaml'
157
127
  import { useDesktopDownload } from '../../hooks/useDesktopDownload'
158
128
  import { useCompareLauncher } from '../compare/useCompareLauncher'
@@ -179,41 +149,6 @@ export function supportsBatchExecution(kind: string, apiKind: string, group?: st
179
149
  return true
180
150
  }
181
151
 
182
- // Stable reference — web renderer wrappers inject platform hooks internally
183
- const rendererOverrides: RendererOverrides = {
184
- RayJobRenderer,
185
- JobRenderer,
186
- JobSetRenderer,
187
- RayServiceRenderer,
188
- RayClusterRenderer,
189
- KueueWorkloadRenderer,
190
- CAPIClusterRenderer,
191
- PodRenderer,
192
- KarpenterNodePoolRenderer,
193
- NodeRenderer,
194
- ServiceRenderer,
195
- WorkloadRenderer,
196
- CompositeRenderer,
197
- ServiceAccountRenderer,
198
- RoleRenderer,
199
- RoleBindingRenderer,
200
- NamespaceRenderer,
201
- HPARenderer,
202
- PVCRenderer,
203
- RolloutRenderer,
204
- KyvernoPolicyCoverage,
205
- KyvernoPolicyQueued,
206
- CNPGObjectStoreRenderer,
207
- VeleroBSLRenderer,
208
- VeleroBackupRenderer,
209
- VeleroRestoreRenderer,
210
- CNPGClusterRenderer,
211
- CNPGDatabaseRenderer,
212
- CNPGPublicationRenderer,
213
- CNPGSubscriptionRenderer,
214
- CNPGImageCatalogRenderer,
215
- }
216
-
217
152
  // ============================================================================
218
153
  // ROUTE WRAPPER — parses kind/ns/name from URL
219
154
  // ============================================================================
@@ -282,16 +217,8 @@ export function WorkloadViewRoute({ onNavigateToResource }: WorkloadViewRoutePro
282
217
  )
283
218
  }
284
219
 
285
- const cnpgPlural = cnpgDetailKindFor(kind, group)
286
- if (cnpgPlural) {
287
- const params = new URLSearchParams(searchParams)
288
- params.delete('apiGroup')
289
- const tab = params.get('tab')
290
- if (cnpgPlural === 'clusters' && (tab === 'timeline' || tab === 'events')) params.set('tab', 'activity')
291
- const base = cnpgDetailPath({ plural: cnpgPlural, namespace, name })
292
- const qs = params.toString()
293
- return <Navigate replace to={qs ? `${base}?${qs}` : base} state={location.state} />
294
- }
220
+ const detailPath = resourceDetailRedirect({ kind, group, namespace, name }, searchParams)
221
+ if (detailPath) return <Navigate replace to={detailPath} state={location.state} />
295
222
 
296
223
  return (
297
224
  <WorkloadView
@@ -305,6 +232,10 @@ export function WorkloadViewRoute({ onNavigateToResource }: WorkloadViewRoutePro
305
232
  )
306
233
  }
307
234
 
235
+ // The kinds the traffic map draws as a node of their own: those that own
236
+ // pods, which it groups under them.
237
+ const TRAFFIC_WORKLOAD_PLURALS = new Set(['deployments', 'statefulsets', 'daemonsets', 'rollouts', 'cronjobs', 'jobs'])
238
+
308
239
  // ============================================================================
309
240
  // WORKLOAD VIEW WRAPPER — injects data fetching hooks
310
241
  // ============================================================================
@@ -330,6 +261,16 @@ interface WorkloadViewProps {
330
261
  pushTabHistory?: boolean
331
262
  breadcrumb?: ReactNode
332
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
333
274
  }
334
275
 
335
276
  interface ImageTargetOwnershipContext {
@@ -340,7 +281,6 @@ interface ImageTargetOwnershipContext {
340
281
  }
341
282
  target: WorkloadImageTarget
342
283
  response: ResourceWithRelationships<Record<string, unknown>>
343
- inheritedResponse?: ResourceWithRelationships<Record<string, unknown>>
344
284
  }
345
285
 
346
286
  function useActionsBarProps(
@@ -453,7 +393,7 @@ function useActionsBarProps(
453
393
  name: string
454
394
  className?: string
455
395
  }) => <PortForwardButton type={type} namespace={ns} name={n} className={className} />,
456
- renderDiagnose,
396
+ renderDiagnose: decorateResourceDiagnose(renderDiagnose),
457
397
  onDelete: (
458
398
  params: Parameters<typeof deleteMutation.mutate>[0],
459
399
  callbacks?: { onSuccess?: () => void },
@@ -461,7 +401,8 @@ function useActionsBarProps(
461
401
  isDeleting: deleteMutation.isPending,
462
402
  cascadeDependents: cascadePreview?.dependents,
463
403
  cascadeLoading,
464
- cascadeRootResolved: cascadeError ? false : cascadePreview?.rootResolved,
404
+ cascadeRootResolved: cascadePreview?.rootResolved,
405
+ cascadePreviewFailed: Boolean(cascadeError),
465
406
  onRestart: rolloutAllows('restart')
466
407
  ? (params: Parameters<typeof restartWorkloadMutation.mutate>[0]) =>
467
408
  restartWorkloadMutation.mutate(params)
@@ -560,6 +501,8 @@ export function WorkloadView({
560
501
  const [imageTargetOwnership, setImageTargetOwnership] =
561
502
  useState<ImageTargetOwnershipContext | null>(null)
562
503
  const imageOwnershipRequestRef = useRef(0)
504
+ // The image guard's evidence is read only once the user opens the dialog.
505
+ const [imageGuardRequested, setImageGuardRequested] = useState(false)
563
506
 
564
507
  // Tab state from URL query param — migrate legacy tab names
565
508
  const rawTab = searchParams.get('tab')
@@ -633,9 +576,11 @@ export function WorkloadView({
633
576
  () => (isDiagnoseKind(apiKind, effectiveGroup) ? [] : (relationships?.services ?? [])),
634
577
  [apiKind, effectiveGroup, relationships],
635
578
  )
579
+ const onRefresh = rest.onRefresh
636
580
  const refetchResourceAndRuns = useCallback(async () => {
637
581
  await Promise.all([
638
582
  refetchResource(),
583
+ onRefresh?.(),
639
584
  ...(apiKind === 'rayjobs' && effectiveGroup === 'ray.io' ? [
640
585
  queryClient.refetchQueries({ queryKey: ['resource', 'jobs', namespace, name, 'batch'], type: 'active' }),
641
586
  ...(resource?.spec?.clusterSelector?.['ray.io/cluster'] ? [queryClient.refetchQueries({ queryKey: ['resource', 'rayclusters', namespace, resource.spec.clusterSelector['ray.io/cluster'], 'ray.io'], type: 'active' })] : []),
@@ -649,7 +594,7 @@ export function WorkloadView({
649
594
  ...(apiKind === 'rayclusters' ? { type: 'active' as const } : {}),
650
595
  }),
651
596
  ])
652
- }, [apiKind, effectiveGroup, name, namespace, queryClient, refetchResource, resource?.spec?.clusterSelector])
597
+ }, [apiKind, effectiveGroup, name, namespace, queryClient, refetchResource, resource?.spec?.clusterSelector, onRefresh])
653
598
  const podWorkloadOwner = useMemo(
654
599
  () => podWorkloadOwnerFromRelationships(apiKind, namespace, relationships, resource),
655
600
  [apiKind, namespace, relationships, resource],
@@ -674,125 +619,33 @@ export function WorkloadView({
674
619
  : null,
675
620
  [apiKind, imageTargetOwnership, name, namespace],
676
621
  )
677
- const relationshipGitopsOwner = useMemo(
678
- () => gitOpsOwnerFromRelationships(relationships),
679
- [relationships],
680
- )
681
- const inheritedGitOpsLookupRef = useMemo(
682
- () =>
683
- findInheritedGitOpsLookupRef(relationships, relationshipGitopsOwner, {
684
- kind: apiKind,
685
- namespace,
686
- name,
687
- group: rest.group,
688
- }),
689
- [relationships, relationshipGitopsOwner, apiKind, namespace, name, rest.group],
690
- )
691
- const inheritedGitOpsResponse = useResourceWithRelationships<any>(
692
- inheritedGitOpsLookupRef ? kindToPluralWithGroup(inheritedGitOpsLookupRef.kind, inheritedGitOpsLookupRef.group ?? '') : '',
693
- inheritedGitOpsLookupRef?.namespace ?? '',
694
- inheritedGitOpsLookupRef?.name ?? '',
695
- inheritedGitOpsLookupRef?.group,
696
- )
697
- const inheritedGitopsOwner = useMemo(
698
- () => gitOpsOwnerFromRelationships(inheritedGitOpsResponse.data?.relationships),
699
- [inheritedGitOpsResponse.data?.relationships],
700
- )
701
- const relationshipHelmOwner = useMemo(
702
- () =>
703
- nativeHelmOwnerFromRelationships(relationships, resource?.metadata?.namespace ?? namespace),
704
- [relationships, resource?.metadata?.namespace, namespace],
705
- )
706
- const inheritedHelmOwner = useMemo(
707
- () =>
708
- nativeHelmOwnerFromRelationships(
709
- inheritedGitOpsResponse.data?.relationships,
710
- inheritedGitOpsResponse.data?.resource?.metadata?.namespace ?? namespace,
711
- ),
712
- [
713
- inheritedGitOpsResponse.data?.relationships,
714
- inheritedGitOpsResponse.data?.resource?.metadata?.namespace,
715
- namespace,
716
- ],
717
- )
718
- const rawGitopsOwner = relationshipGitopsOwner ?? inheritedGitopsOwner
719
- const gitOpsSourceResource = relationshipGitopsOwner
720
- ? resource
721
- : inheritedGitOpsResponse.data?.resource
722
- const helmOwner = relationshipHelmOwner ?? inheritedHelmOwner
723
- const helmSourceResource = relationshipHelmOwner
724
- ? resource
725
- : inheritedGitOpsResponse.data?.resource
726
- const targetRelationshipGitopsOwner = useMemo(
727
- () => gitOpsOwnerFromRelationships(activeImageTargetOwnership?.response.relationships),
728
- [activeImageTargetOwnership?.response.relationships],
729
- )
730
- const targetInheritedGitopsOwner = useMemo(
731
- () =>
732
- gitOpsOwnerFromRelationships(
733
- activeImageTargetOwnership?.inheritedResponse?.relationships,
734
- ),
735
- [activeImageTargetOwnership?.inheritedResponse?.relationships],
736
- )
737
- const targetRawGitopsOwner = targetRelationshipGitopsOwner ?? targetInheritedGitopsOwner
738
- const targetRelationshipHelmOwner = nativeHelmOwnerFromRelationships(
739
- activeImageTargetOwnership?.response.relationships,
740
- activeImageTargetOwnership?.target.namespace ?? namespace,
741
- )
742
- const targetInheritedHelmOwner = nativeHelmOwnerFromRelationships(
743
- activeImageTargetOwnership?.inheritedResponse?.relationships,
744
- activeImageTargetOwnership?.target.namespace ?? namespace,
745
- )
746
- const targetHelmOwner = targetRelationshipHelmOwner ?? targetInheritedHelmOwner
747
- const shouldResolveArgoOwner =
748
- (rawGitopsOwner?.tool === 'argocd' && !rawGitopsOwner.namespace) ||
749
- (targetRawGitopsOwner?.tool === 'argocd' && !targetRawGitopsOwner.namespace)
750
- const { data: argoApplications } = useResources<any>('applications', undefined, 'argoproj.io', {
751
- enabled: shouldResolveArgoOwner,
622
+ const workloadOwnership = useResolvedGitOpsOwner({
623
+ kind: apiKind,
624
+ group: rest.group,
625
+ namespace,
626
+ name,
627
+ relationships,
628
+ resource,
629
+ })
630
+ const {
631
+ owner: gitopsOwner,
632
+ ownerObject: gitopsOwnerObject,
633
+ ownerVerified: gitOpsOwnerVerified,
634
+ ownerPending: gitOpsOwnerPending,
635
+ ownerSource: gitOpsOwnerSource,
636
+ ownerStatus: gitOpsOwnerStatus,
637
+ helmOwner,
638
+ helmSource: helmOwnerSource,
639
+ } = workloadOwnership
640
+ const imageTargetOwnershipResolution = useResolvedGitOpsOwner({
641
+ kind: activeImageTargetOwnership?.target.resource ?? '',
642
+ group: activeImageTargetOwnership?.target.group,
643
+ namespace: activeImageTargetOwnership?.target.namespace ?? '',
644
+ name: activeImageTargetOwnership?.target.name ?? '',
645
+ relationships: activeImageTargetOwnership?.response.relationships,
646
+ resource: activeImageTargetOwnership?.response.resource,
647
+ enabled: Boolean(activeImageTargetOwnership),
752
648
  })
753
- const gitopsOwner = useMemo(
754
- () => resolveGitOpsOwner(rawGitopsOwner, argoApplications),
755
- [rawGitopsOwner, argoApplications],
756
- )
757
- const targetGitopsOwner = useMemo(
758
- () => resolveGitOpsOwner(targetRawGitopsOwner, argoApplications),
759
- [argoApplications, targetRawGitopsOwner],
760
- )
761
- const gitopsOwnerGroup = gitopsOwner ? gitOpsOwnerGroup(gitopsOwner) : ''
762
- const shouldFetchGitOpsOwner = Boolean(gitopsOwner?.namespace)
763
- const gitopsOwnerQuery = useResource<any>(
764
- shouldFetchGitOpsOwner ? gitopsOwner!.kind : '',
765
- gitopsOwner?.namespace ?? '',
766
- gitopsOwner?.name ?? '',
767
- gitopsOwnerGroup,
768
- )
769
- const targetGitopsOwnerGroup = targetGitopsOwner ? gitOpsOwnerGroup(targetGitopsOwner) : ''
770
- const shouldFetchTargetGitOpsOwner = Boolean(
771
- activeImageTargetOwnership && targetGitopsOwner?.namespace,
772
- )
773
- const targetGitopsOwnerQuery = useResource<Record<string, unknown>>(
774
- shouldFetchTargetGitOpsOwner ? targetGitopsOwner!.kind : '',
775
- targetGitopsOwner?.namespace ?? '',
776
- targetGitopsOwner?.name ?? '',
777
- targetGitopsOwnerGroup,
778
- { enabled: shouldFetchTargetGitOpsOwner },
779
- )
780
- const gitOpsOwnerStatus = useMemo(
781
- () => deriveGitOpsOwnerStatus(gitopsOwner, gitopsOwnerQuery.data),
782
- [gitopsOwner, gitopsOwnerQuery.data],
783
- )
784
- const gitOpsOwnerVerified = Boolean(gitopsOwner?.namespace && gitopsOwnerQuery.data)
785
- const gitOpsOwnerPending = Boolean(
786
- gitopsOwner?.namespace && gitopsOwnerQuery.isLoading && !gitopsOwnerQuery.data,
787
- )
788
- const gitOpsOwnerSource = useMemo(
789
- () => describeGitOpsOwnerSource(rawGitopsOwner, gitOpsSourceResource),
790
- [rawGitopsOwner, gitOpsSourceResource],
791
- )
792
- const helmOwnerSource = useMemo(
793
- () => describeHelmOwnerSource(helmOwner, helmSourceResource),
794
- [helmOwner, helmSourceResource],
795
- )
796
649
 
797
650
  // Fetch topology for hierarchy building (only when expanded). Polled like
798
651
  // useTrace's "drawer feeling live" pattern — without this, a resource
@@ -989,6 +842,23 @@ export function WorkloadView({
989
842
  (path: string) => navigateRouter(path),
990
843
  [navigateRouter],
991
844
  )
845
+ // Offered for the kinds the traffic map draws as one node, and only once a
846
+ // traffic source is known to be available on this cluster. A Job something
847
+ // controls (a CronJob, a JobSet) is not one: the map groups its pods under
848
+ // that owner, so it is offered only once the Job is known to have none.
849
+ const isJob = apiKind.toLowerCase() === 'jobs'
850
+ const controlledJob = isJob && (!resource ||
851
+ (resource.metadata?.ownerReferences ?? []).some((ref: { controller?: boolean }) => ref.controller))
852
+ const trafficKind = !!namespace && TRAFFIC_WORKLOAD_PLURALS.has(apiKind.toLowerCase()) && !controlledJob
853
+ const trafficAvailable = useTrafficAvailable(trafficKind)
854
+ const openLiveTraffic = useCallback(() => {
855
+ const params = new URLSearchParams()
856
+ const namespaces = searchParams.get('namespaces')
857
+ if (namespaces) params.set('namespaces', namespaces)
858
+ params.set('focus', `${namespace}/${name}`)
859
+ navigateRouter({ pathname: '/traffic', search: params.toString() })
860
+ }, [navigateRouter, searchParams, namespace, name])
861
+ const offerLiveTraffic = trafficKind && trafficAvailable
992
862
  // Drawer TraceSummary CTA → open the full resource view ON the Reachability tab.
993
863
  // The generic onExpand navigates to the workload path but drops the query, so we
994
864
  // navigate directly to that path WITH ?tab=reachability - the deeplink the
@@ -1013,6 +883,10 @@ export function WorkloadView({
1013
883
  const loadImagesWithTargetOwnership = useCallback(
1014
884
  async (params: { kind: string; namespace: string; name: string }) => {
1015
885
  const request = ++imageOwnershipRequestRef.current
886
+ setImageGuardRequested(true)
887
+ // Each open and refresh re-reads the evidence: the owner's sync policy
888
+ // or ignore rules may have changed since the dialog last opened.
889
+ void queryClient.invalidateQueries({ queryKey: ['gitops-write-evidence'] })
1016
890
  const inventory = await baseActionsBarProps.onLoadImages!(params)
1017
891
  const targetDiffers =
1018
892
  inventory.target.resource.toLowerCase() !== params.kind.toLowerCase() ||
@@ -1058,14 +932,17 @@ export function WorkloadView({
1058
932
  group: inventory.target.group,
1059
933
  },
1060
934
  )
1061
- const inheritedResponse = inheritedRef
1062
- ? await fetchRelationships(
1063
- kindToPluralWithGroup(inheritedRef.kind, inheritedRef.group ?? ''),
1064
- inheritedRef.namespace,
1065
- inheritedRef.name,
1066
- inheritedRef.group,
1067
- )
1068
- : undefined
935
+ // Warm the cache the target's ownership resolution reads, so the
936
+ // dialog doesn't open on a still-pending inherited lookup. A failed
937
+ // read surfaces there as unknown ownership, not as a failed load.
938
+ if (inheritedRef) {
939
+ await fetchRelationships(
940
+ kindToPluralWithGroup(inheritedRef.kind, inheritedRef.group ?? ''),
941
+ inheritedRef.namespace,
942
+ inheritedRef.name,
943
+ inheritedRef.group,
944
+ ).catch(() => undefined)
945
+ }
1069
946
  if (request === imageOwnershipRequestRef.current) {
1070
947
  setImageTargetOwnership({
1071
948
  root: {
@@ -1075,52 +952,74 @@ export function WorkloadView({
1075
952
  },
1076
953
  target: inventory.target,
1077
954
  response,
1078
- inheritedResponse,
1079
955
  })
1080
956
  }
1081
957
  return inventory
1082
958
  },
1083
959
  [baseActionsBarProps.onLoadImages, queryClient, setImageTargetOwnership],
1084
960
  )
1085
- const imageGitopsOwner = activeImageTargetOwnership ? targetGitopsOwner : gitopsOwner
1086
- const imageHelmOwner = activeImageTargetOwnership ? targetHelmOwner : helmOwner
1087
- const imageGitopsOwnerData = activeImageTargetOwnership
1088
- ? targetGitopsOwnerQuery.data
1089
- : gitopsOwnerQuery.data
1090
- const managedImageSources = useMemo<ManagedImageSource[] | undefined>(() => {
1091
- if (!activeImageTargetOwnership && inheritedGitOpsLookupRef && (inheritedGitOpsResponse.isPending || inheritedGitOpsResponse.isError)) {
1092
- return undefined
1093
- }
1094
- const sources: ManagedImageSource[] = []
1095
- if (imageGitopsOwner) {
1096
- sources.push({
1097
- type: 'GitOps',
1098
- label: imageGitopsOwner.namespace
1099
- ? `${imageGitopsOwner.namespace}/${imageGitopsOwner.name}`
1100
- : imageGitopsOwner.name,
1101
- onOpen: imageGitopsOwnerData
1102
- ? () => handleOpenGitOpsResource(imageGitopsOwner)
961
+ const imageOwnershipSource = activeImageTargetOwnership ? imageTargetOwnershipResolution : workloadOwnership
962
+ const imageGuardTarget = activeImageTargetOwnership
963
+ ? {
964
+ kind: activeImageTargetOwnership.target.kind,
965
+ group: activeImageTargetOwnership.target.group,
966
+ namespace: activeImageTargetOwnership.target.namespace,
967
+ name: activeImageTargetOwnership.target.name,
968
+ }
969
+ : { kind: resource?.kind ?? pluralToKind(apiKind), group: effectiveGroup ?? '', namespace, name }
970
+ // Once the dialog lists the containers, ask about each by name (so the
971
+ // evidence can't predate the list) and judge the ones being changed.
972
+ const [imageSelection, setImageSelection] = useState<SetImageSelection | null>(null)
973
+ const imageWrites = useMemo(
974
+ () =>
975
+ imageSelection
976
+ ? setImageWrites(imageSelection.changed.length ? imageSelection.changed : imageSelection.containers)
977
+ : SET_IMAGE_WRITES,
978
+ [imageSelection],
979
+ )
980
+ const imageEvidencePaths = useMemo(
981
+ () => (imageSelection ? gitOpsWriteEvidencePaths(setImageWrites(imageSelection.containers)) : undefined),
982
+ [imageSelection],
983
+ )
984
+ const { guard: imageGuard, ownership: imageGuardOwnership } = useGitOpsWriteGuard({
985
+ target: imageGuardTarget,
986
+ writes: imageWrites,
987
+ evidencePaths: imageEvidencePaths,
988
+ ownership: imageOwnershipSource,
989
+ relationshipsUnavailable: activeImageTargetOwnership
990
+ ? !activeImageTargetOwnership.response.relationships
991
+ : Boolean(resource) && !relationships,
992
+ enabled: imageGuardRequested,
993
+ })
994
+ const {
995
+ owner: imageOwner,
996
+ helmOwner: imageHelmOwner,
997
+ ownerVerified: imageOwnerVerified,
998
+ } = imageGuardOwnership
999
+ const imageOwnership = useMemo<SetImageOwnership | undefined>(() => {
1000
+ if (!imageGuard) return undefined
1001
+ const owner = imageOwner
1002
+ const helmOwner = imageHelmOwner
1003
+ return {
1004
+ guard: imageGuard,
1005
+ onSelectionChange: setImageSelection,
1006
+ selectionKey: setImageSelectionKey(imageSelection),
1007
+ onOpenOwner: owner
1008
+ ? imageOwnerVerified
1009
+ ? () => handleOpenGitOpsResource(owner)
1010
+ : undefined
1011
+ : helmOwner
1012
+ ? () => handleOpenHelmRelease(helmOwner)
1103
1013
  : undefined,
1104
- })
1105
1014
  }
1106
- if (imageHelmOwner) {
1107
- sources.push({
1108
- type: 'Helm',
1109
- label: `${imageHelmOwner.namespace}/${imageHelmOwner.name}`,
1110
- onOpen: () => handleOpenHelmRelease(imageHelmOwner),
1111
- })
1112
- }
1113
- return sources
1114
1015
  }, [
1115
1016
  handleOpenGitOpsResource,
1116
1017
  handleOpenHelmRelease,
1117
- activeImageTargetOwnership,
1118
- imageGitopsOwner,
1119
- imageGitopsOwnerData,
1018
+ imageGuard,
1019
+ imageOwner,
1120
1020
  imageHelmOwner,
1121
- inheritedGitOpsLookupRef,
1122
- inheritedGitOpsResponse.isError,
1123
- inheritedGitOpsResponse.isPending,
1021
+ imageOwnerVerified,
1022
+ imageSelection,
1124
1023
  ])
1125
1024
  const actionsBarProps = useMemo(
1126
1025
  () => ({
@@ -1130,12 +1029,12 @@ export function WorkloadView({
1130
1029
  : undefined,
1131
1030
  onCompareTo,
1132
1031
  onCompareAcrossClusters,
1133
- managedImageSources,
1032
+ imageOwnership,
1134
1033
  }),
1135
1034
  [
1136
1035
  baseActionsBarProps,
1137
1036
  loadImagesWithTargetOwnership,
1138
- managedImageSources,
1037
+ imageOwnership,
1139
1038
  onCompareTo,
1140
1039
  onCompareAcrossClusters,
1141
1040
  ],
@@ -1283,9 +1182,7 @@ export function WorkloadView({
1283
1182
  onSelectRun={handleSelectedRunChange}
1284
1183
  />
1285
1184
  )}
1286
- renderSummary={({ apiKind: ak, namespace: ns, name: n, resource: res, context, onNavigate }) =>
1287
- renderCNPGSummary({ apiKind: ak, namespace: ns, name: n, group: effectiveGroup, resource: res, context, onNavigate })
1288
- }
1185
+ {...resourceDetailSlots({ kind: apiKind, group: resource?.apiVersion ? resourceGroup : effectiveGroup, namespace, name }, rest.extraTabs)}
1289
1186
  renderExpandedOverview={({ kind: k, apiKind, namespace: ns, name: n, resource: res }) =>
1290
1187
  supportsBatchExecution(k, apiKind, effectiveGroup, res?.apiVersion) &&
1291
1188
  res ? (
@@ -1350,7 +1247,7 @@ export function WorkloadView({
1350
1247
  onDuplicate={handleDuplicate}
1351
1248
  onDownload={desktopDownload}
1352
1249
  actionsBarProps={actionsBarProps}
1353
- rendererOverrides={rendererOverrides}
1250
+ rendererOverrides={resourceRendererOverrides}
1354
1251
  renderOverviewExtra={({ kind: k, namespace: ns, name: n, group: g, context }) => {
1355
1252
  // Network entry kinds (Service/Ingress/Route/Gateway) ARE the diagnosis
1356
1253
  // target: DiagnoseInlineSection renders in the drawer, no hint. Workload
@@ -1368,7 +1265,6 @@ export function WorkloadView({
1368
1265
  <>
1369
1266
  {diagnoseInline}
1370
1267
  {diagnoseHint}
1371
- <FluxSourceConsumersSection kind={k} namespace={ns} name={n} />
1372
1268
  <AuditOverviewSection
1373
1269
  findings={auditFindings ?? []}
1374
1270
  onViewAll={() => navigateRouter('/checks')}
@@ -1379,6 +1275,7 @@ export function WorkloadView({
1379
1275
  renderOverviewLead={() => (
1380
1276
  <ResourceIssuesSection
1381
1277
  issues={liveIssues}
1278
+ compact={!expanded}
1382
1279
  subjectResource={{ kind: apiKind, namespace, name, group: rest.group }}
1383
1280
  onResourceClick={
1384
1281
  rest.onNavigateToResource
@@ -1395,7 +1292,7 @@ export function WorkloadView({
1395
1292
  )}
1396
1293
  hasOperationalIssues={hasOperationalIssues}
1397
1294
  operationalIssuesPending={issuesPending}
1398
- onOpenGitOpsResource={gitopsOwnerQuery.data ? handleOpenGitOpsResource : undefined}
1295
+ onOpenGitOpsResource={gitopsOwnerObject ? handleOpenGitOpsResource : undefined}
1399
1296
  resolvedGitOpsOwner={gitopsOwner}
1400
1297
  gitOpsOwnerVerified={gitOpsOwnerVerified}
1401
1298
  gitOpsOwnerPending={gitOpsOwnerPending}
@@ -1405,6 +1302,7 @@ export function WorkloadView({
1405
1302
  helmOwnerSource={helmOwnerSource}
1406
1303
  onOpenHelmRelease={handleOpenHelmRelease}
1407
1304
  onNavigateGitOpsPath={handleNavigateGitOpsPath}
1305
+ onOpenLiveTraffic={offerLiveTraffic ? openLiveTraffic : undefined}
1408
1306
  />
1409
1307
  <CreateResourceDialog
1410
1308
  open={duplicateDialogOpen}
@@ -1426,55 +1324,17 @@ export function WorkloadView({
1426
1324
  )
1427
1325
  }
1428
1326
 
1429
- function collectServingRefs(relationships: Relationships | undefined): ResourceRef[] {
1327
+ export function collectServingRefs(relationships: Relationships | undefined): ResourceRef[] {
1430
1328
  if (!relationships) return []
1431
- return dedupeRefs([
1329
+ return dedupeResourceRefs([
1432
1330
  ...(relationships.services ?? []),
1433
1331
  ...(relationships.ingresses ?? []),
1434
1332
  ...(relationships.gateways ?? []),
1435
1333
  ...(relationships.routes ?? []),
1334
+ ...(relationships.routedFrom ?? []),
1436
1335
  ])
1437
1336
  }
1438
1337
 
1439
- function dedupeRefs(refs: ResourceRef[]): ResourceRef[] {
1440
- const seen = new Set<string>()
1441
- return refs.filter((ref) => {
1442
- const key = `${ref.kind}/${ref.namespace}/${ref.name}/${ref.group ?? ''}`
1443
- if (seen.has(key)) return false
1444
- seen.add(key)
1445
- return true
1446
- })
1447
- }
1448
-
1449
- function resolveGitOpsOwner(
1450
- owner: GitOpsOwnerRef | null,
1451
- argoApplications: any[] | undefined,
1452
- ): GitOpsOwnerRef | null {
1453
- if (!owner || owner.namespace || owner.tool !== 'argocd') return owner
1454
- const matches = (argoApplications ?? []).filter((app) => app?.metadata?.name === owner.name)
1455
- if (matches.length !== 1) return owner
1456
- const namespace = matches[0]?.metadata?.namespace
1457
- return namespace ? { ...owner, namespace } : owner
1458
- }
1459
-
1460
- export function findInheritedGitOpsLookupRef(
1461
- relationships: Relationships | undefined,
1462
- directOwner: GitOpsOwnerRef | null,
1463
- current: ResourceRef,
1464
- ): ResourceRef | null {
1465
- if (directOwner) return null
1466
- const inheritedManagerRefs = (relationships?.managedBy ?? []).filter(
1467
- (ref) => !gitOpsOwnerFromRelationships({ managedBy: [ref] }) && !isNativeHelmManager(ref),
1468
- )
1469
- const candidates = [
1470
- relationships?.deployment,
1471
- ...inheritedManagerRefs,
1472
- relationships?.owner,
1473
- ].filter(Boolean) as ResourceRef[]
1474
-
1475
- return candidates.find((ref) => !isCurrentResource(ref, current)) ?? null
1476
- }
1477
-
1478
1338
  const POD_OWNERSHIP_WORKLOAD_KINDS = new Set([
1479
1339
  'deployments',
1480
1340
  'statefulsets',
@@ -1554,89 +1414,6 @@ function sameWorkload(
1554
1414
  )
1555
1415
  }
1556
1416
 
1557
- function nativeHelmOwnerFromRelationships(
1558
- relationships: Relationships | undefined,
1559
- fallbackNamespace: string,
1560
- ): HelmOwnerRef | null {
1561
- const ref = relationships?.managedBy?.[0]
1562
- if (!ref || !isNativeHelmManager(ref)) return null
1563
- return {
1564
- namespace: ref.namespace || fallbackNamespace,
1565
- name: ref.name,
1566
- }
1567
- }
1568
-
1569
- function isCurrentResource(ref: ResourceRef, current: ResourceRef): boolean {
1570
- return (
1571
- kindToPluralWithGroup(ref.kind, ref.group ?? '') ===
1572
- kindToPluralWithGroup(current.kind, current.group ?? '') &&
1573
- ref.namespace === current.namespace &&
1574
- ref.name === current.name &&
1575
- (ref.group ?? '') === (current.group ?? '')
1576
- )
1577
- }
1578
-
1579
- function isNativeHelmManager(ref: ResourceRef): boolean {
1580
- return ref.kind === 'HelmRelease' && ref.group !== 'helm.toolkit.fluxcd.io'
1581
- }
1582
-
1583
- function describeGitOpsOwnerSource(owner: GitOpsOwnerRef | null, resource: any): string | null {
1584
- if (!owner || !resource) return null
1585
- const labels = resource.metadata?.labels ?? {}
1586
- const annotations = resource.metadata?.annotations ?? {}
1587
-
1588
- if (owner.tool === 'fluxcd') {
1589
- const nameKey =
1590
- owner.kind === 'helmreleases'
1591
- ? 'helm.toolkit.fluxcd.io/name'
1592
- : 'kustomize.toolkit.fluxcd.io/name'
1593
- const nsKey =
1594
- owner.kind === 'helmreleases'
1595
- ? 'helm.toolkit.fluxcd.io/namespace'
1596
- : 'kustomize.toolkit.fluxcd.io/namespace'
1597
- if (labels[nameKey] || labels[nsKey]) {
1598
- return `${nameKey}=${labels[nameKey] ?? ''}, ${nsKey}=${labels[nsKey] ?? ''}`
1599
- }
1600
- }
1601
-
1602
- const trackingID = annotations['argocd.argoproj.io/tracking-id']
1603
- if (trackingID) return `argocd.argoproj.io/tracking-id=${trackingID}`
1604
- const argoInstance = labels['argocd.argoproj.io/instance']
1605
- if (argoInstance) return `argocd.argoproj.io/instance=${argoInstance}`
1606
- return null
1607
- }
1608
-
1609
- function describeHelmOwnerSource(owner: HelmOwnerRef | null, resource: any): string | null {
1610
- if (!owner || !resource) return null
1611
- const annotations = resource.metadata?.annotations ?? {}
1612
- const releaseName = annotations['meta.helm.sh/release-name']
1613
- const releaseNamespace = annotations['meta.helm.sh/release-namespace']
1614
- if (releaseName || releaseNamespace) {
1615
- return `meta.helm.sh/release-name=${releaseName ?? ''}, meta.helm.sh/release-namespace=${releaseNamespace ?? ''}`
1616
- }
1617
- return null
1618
- }
1619
-
1620
- function gitOpsOwnerGroup(owner: GitOpsOwnerRef): string {
1621
- if (owner.tool === 'argocd') return 'argoproj.io'
1622
- if (owner.kind === 'kustomizations') return 'kustomize.toolkit.fluxcd.io'
1623
- return 'helm.toolkit.fluxcd.io'
1624
- }
1625
-
1626
- function deriveGitOpsOwnerStatus(owner: GitOpsOwnerRef | null, resource: any): GitOpsStatus | null {
1627
- if (!owner || !resource || !hasGitOpsStatusPayload(owner, resource)) return null
1628
- return getGitOpsResourceStatus(owner.kind, resource)
1629
- }
1630
-
1631
- function hasGitOpsStatusPayload(owner: GitOpsOwnerRef, resource: any): boolean {
1632
- if (owner.kind === 'applications') {
1633
- const status = resource.status ?? {}
1634
- return Boolean(status.sync?.status || status.health?.status || status.operationState?.phase)
1635
- }
1636
- if (resource.spec?.suspend === true) return true
1637
- return Array.isArray(resource.status?.conditions) && resource.status.conditions.length > 0
1638
- }
1639
-
1640
1417
  // ============================================================================
1641
1418
  // LOGS TAB — platform-specific (uses data-fetching hooks)
1642
1419
  // ============================================================================
@@ -1694,9 +1471,8 @@ function LogsTabContent({
1694
1471
  )
1695
1472
  }
1696
1473
 
1697
- if (kind === 'Cluster' && isApiGroup(resource?.apiVersion, 'postgresql.cnpg.io')) {
1698
- return <CNPGClusterLogs namespace={namespace} name={name} />
1699
- }
1474
+ const integrationLogs = resourceLogs({ kind, group, namespace, name }, resource)
1475
+ if (integrationLogs) return integrationLogs
1700
1476
 
1701
1477
  // Workload kinds with stable pod selectors use the aggregated workload logs viewer
1702
1478
  if (WORKLOAD_LOG_KINDS.has(kind) && (kind !== 'Job' || isCoreBatchJob(apiKind, group))) {
@@ -2372,137 +2148,6 @@ function DiagnoseInlineSection({ kind, namespace, name, group, onOpenReachabilit
2372
2148
  )
2373
2149
  }
2374
2150
 
2375
- // FluxSourceConsumersSection lists the reconcilers (Kustomization, HelmRelease)
2376
- // that reference this Flux source CR — the inverse of `spec.sourceRef`. Renders
2377
- // only when the focused resource is a Flux source kind; otherwise null. Sources
2378
- // can have many consumers (one repo feeding multiple apps), so this answers
2379
- // "if I edit this source, what gets affected on the next reconcile?".
2380
- //
2381
- // Filtering happens client-side off the namespaced reconciler lists — these
2382
- // are typically small (tens, not thousands) and the dynamic informer cache
2383
- // makes the request cheap. If a cluster ever has thousands of HelmReleases,
2384
- // a dedicated /api/gitops/consumers endpoint would be the right move; today
2385
- // it'd be premature.
2386
- // Outer component is cheap — it does only the kind check and decides whether
2387
- // to mount the data-fetching child. Without this split, useResources would
2388
- // fire two API calls on EVERY workload drawer open (Pod, Deployment, Service,
2389
- // …), since the hook has no `enabled` flag and can't be conditionally called
2390
- // (Rules of Hooks). The hooks only need to run when the focused resource is
2391
- // actually a Flux source CR.
2392
- function FluxSourceConsumersSection({
2393
- kind,
2394
- namespace,
2395
- name,
2396
- }: {
2397
- kind: string
2398
- namespace: string
2399
- name: string
2400
- }) {
2401
- // The inner WorkloadView de-pluralizes the URL's plural form, which gives
2402
- // "Gitrepository" (single-uppercase) rather than the wire-correct
2403
- // "GitRepository" — so we match lowercase. spec.sourceRef.kind on consumers
2404
- // is always wire-correct, so we look that up separately.
2405
- const sourceKind = FLUX_SOURCE_KIND_BY_LOWER.get(kind.toLowerCase()) ?? null
2406
- if (!sourceKind) return null
2407
- return <FluxSourceConsumersInner sourceKind={sourceKind} namespace={namespace} name={name} />
2408
- }
2409
-
2410
- function FluxSourceConsumersInner({
2411
- sourceKind,
2412
- namespace,
2413
- name,
2414
- }: {
2415
- sourceKind: string
2416
- namespace: string
2417
- name: string
2418
- }) {
2419
- const navigate = useNavigate()
2420
- const { data: kustomizations } = useResources<any>(
2421
- 'kustomizations',
2422
- undefined,
2423
- 'kustomize.toolkit.fluxcd.io',
2424
- )
2425
- const { data: helmReleases } = useResources<any>(
2426
- 'helmreleases',
2427
- undefined,
2428
- 'helm.toolkit.fluxcd.io',
2429
- )
2430
-
2431
- const consumers: Array<{
2432
- kind: 'Kustomization' | 'HelmRelease'
2433
- namespace: string
2434
- name: string
2435
- plural: string
2436
- }> = []
2437
- for (const k of kustomizations ?? []) {
2438
- const ref = k?.spec?.sourceRef ?? {}
2439
- const refNs = ref.namespace || k?.metadata?.namespace
2440
- if (ref.kind === sourceKind && ref.name === name && refNs === namespace) {
2441
- consumers.push({
2442
- kind: 'Kustomization',
2443
- namespace: k.metadata.namespace,
2444
- name: k.metadata.name,
2445
- plural: 'kustomizations',
2446
- })
2447
- }
2448
- }
2449
- for (const h of helmReleases ?? []) {
2450
- const ref = h?.spec?.chart?.spec?.sourceRef ?? {}
2451
- const refNs = ref.namespace || h?.metadata?.namespace
2452
- if (ref.kind === sourceKind && ref.name === name && refNs === namespace) {
2453
- consumers.push({
2454
- kind: 'HelmRelease',
2455
- namespace: h.metadata.namespace,
2456
- name: h.metadata.name,
2457
- plural: 'helmreleases',
2458
- })
2459
- }
2460
- }
2461
-
2462
- if (consumers.length === 0) {
2463
- return (
2464
- <section className="rounded-lg border border-theme-border bg-theme-surface p-4 shadow-theme-sm">
2465
- <h3 className="mb-2 text-sm font-semibold text-theme-text-primary">Consumed by</h3>
2466
- <p className="text-xs text-theme-text-tertiary">
2467
- No Kustomization or HelmRelease references this source.
2468
- </p>
2469
- </section>
2470
- )
2471
- }
2472
-
2473
- return (
2474
- <section className="rounded-lg border border-theme-border bg-theme-surface p-4 shadow-theme-sm">
2475
- <h3 className="mb-3 text-sm font-semibold text-theme-text-primary">
2476
- Consumed by ({consumers.length})
2477
- </h3>
2478
- <div className="flex flex-wrap gap-1.5">
2479
- {consumers.map((c) => (
2480
- <Tooltip
2481
- key={`${c.kind}/${c.namespace}/${c.name}`}
2482
- content={`${c.kind} ${c.namespace}/${c.name}`}
2483
- >
2484
- <button
2485
- onClick={() =>
2486
- navigate(
2487
- `/gitops/detail/${c.plural}/${encodeURIComponent(c.namespace)}/${encodeURIComponent(c.name)}`,
2488
- )
2489
- }
2490
- 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"
2491
- >
2492
- <span className="text-theme-text-tertiary">
2493
- {c.kind === 'HelmRelease' ? 'HR' : 'K'}
2494
- </span>
2495
- <span>
2496
- {c.namespace}/{c.name}
2497
- </span>
2498
- </button>
2499
- </Tooltip>
2500
- ))}
2501
- </div>
2502
- </section>
2503
- )
2504
- }
2505
-
2506
2151
  // Drawer mode: single chart + category tabs (compact for ~500px width).
2507
2152
  // Full-screen mode: multi-chart grid so CPU + Memory + Network can be
2508
2153
  // compared side-by-side without tab switching.
@@ -2577,18 +2222,6 @@ function DrawerMetricsContent({
2577
2222
  )
2578
2223
  }
2579
2224
 
2580
- // FLUX_SOURCE_KIND_BY_LOWER maps lowercase kind (what the inner WorkloadView
2581
- // produces via its plural-to-singular fallback) to the wire-correct
2582
- // PascalCase form that consumers carry in spec.sourceRef.kind. HelmChart is
2583
- // intentionally absent — it's an auto-generated internal CR, not something
2584
- // users create or point reconcilers at directly.
2585
- const FLUX_SOURCE_KIND_BY_LOWER = new Map<string, string>([
2586
- ['gitrepository', 'GitRepository'],
2587
- ['helmrepository', 'HelmRepository'],
2588
- ['ocirepository', 'OCIRepository'],
2589
- ['bucket', 'Bucket'],
2590
- ])
2591
-
2592
2225
  // Read-only manifest view for an object in the workload's neighborhood (the
2593
2226
  // YAML tab's object rail). Read-only by design — editing an arbitrary related
2594
2227
  // object belongs on that resource's own page.