@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
@@ -35,7 +35,9 @@ describe('Metrics help', () => {
35
35
  expect(html).toContain('--prometheus-single-cluster')
36
36
  expect(html).toContain('they do not add missing metrics')
37
37
  expect(html).toContain('Leave both unset for automatic matching')
38
- expect(html).toContain('it does not scope rightsizing')
38
+ expect(html).toContain('It does not scope rightsizing')
39
+ expect(html).toContain('Pod comparison and PVC usage')
40
+ expect(html).toContain('labels must exist on the kubelet volume stats')
39
41
  expect(html).toContain('Standalone CLI:')
40
42
  expect(html).toContain('when starting Radar')
41
43
  expect(html).toContain('traffic.prometheusSingleCluster')
@@ -133,7 +133,7 @@ export function WorkloadMetricsHelpContent({ data, pending, error }: Pick<Props,
133
133
  <div><dt className="font-medium">Backend dedicated to this cluster</dt><dd><code className="break-all font-mono text-xs">--prometheus-single-cluster</code><p className="mt-1">Helm: <code className="break-all font-mono text-xs">traffic.prometheusSingleCluster: true</code></p></dd></div>
134
134
  <div><dt className="font-medium">Shared backend with a cluster label</dt><dd><code className="break-all font-mono text-xs">--prometheus-cluster-label cluster=your-cluster</code><p className="mt-1">Helm: set the backend’s actual label and value in <code className="break-all font-mono text-xs">traffic.prometheusClusterLabels</code>.</p></dd></div>
135
135
  </dl>
136
- <p>Leave both unset for automatic matching. An override replaces automatic matching for workload requests, resources, history and Pod comparison only; it does not scope rightsizing or other metrics features.</p>
136
+ <p>Leave both unset for automatic matching. An override replaces automatic matching for workload requests, resources, history, Pod comparison and PVC usage. For PVC usage, its labels must exist on the kubelet volume stats. It does not scope rightsizing, node/HPA or legacy network/storage metrics.</p>
137
137
  <h4 className="font-medium text-theme-text-primary">Operator configuration</h4>
138
138
  <p><strong className="font-medium">Standalone CLI:</strong> supply the flag when starting Radar. Overrides are not saved; after changing the cluster, backend or credentials, verify the new scope before restarting with a fresh override.</p>
139
139
  <p><strong className="font-medium">In-cluster OSS or Radar Cloud:</strong> ask the installation’s operator to update its Helm values or GitOps configuration. These are shared backend settings, not viewer preferences.</p>
@@ -12,7 +12,7 @@ import {
12
12
  type CrossplaneResourceRef,
13
13
  } from '@skyhook-io/k8s-ui/components/resources/resource-utils-crossplane'
14
14
  import { getResourceStatus } from '@skyhook-io/k8s-ui'
15
- import { kindToPlural } from '@skyhook-io/k8s-ui/utils/navigation'
15
+ import { kindToPluralWithGroup } from '@skyhook-io/k8s-ui/utils/navigation'
16
16
  import type { ResourceRef as NavRef } from '../../types'
17
17
  import { fetchJSON, ApiError } from '../../api/client'
18
18
 
@@ -59,8 +59,8 @@ export function CompositeRenderer({ data, onNavigate }: CompositeRendererProps)
59
59
  ],
60
60
  queryFn: async () => {
61
61
  const ns = boundXRRef!.namespace || '_'
62
- const plural = kindToPlural(boundXRRef!.kind)
63
62
  const group = groupFromApiVersion(boundXRRef!.apiVersion)
63
+ const plural = kindToPluralWithGroup(boundXRRef!.kind, group)
64
64
  const query = group ? `?group=${encodeURIComponent(group)}` : ''
65
65
  return fetchJSON<{ resource: any }>(`/resources/${plural}/${ns}/${boundXRRef!.name}${query}`)
66
66
  },
@@ -99,7 +99,7 @@ export function CompositeRenderer({ data, onNavigate }: CompositeRendererProps)
99
99
  queryKey: ['composed-ref', group, ref.kind, ref.namespace ?? '', ref.name],
100
100
  queryFn: async () => {
101
101
  const ns = ref.namespace || '_'
102
- const plural = kindToPlural(ref.kind)
102
+ const plural = kindToPluralWithGroup(ref.kind, group)
103
103
  const query = group ? `?group=${encodeURIComponent(group)}` : ''
104
104
  return fetchJSON<{ resource: any }>(`/resources/${plural}/${ns}/${ref.name}${query}`)
105
105
  },
@@ -137,7 +137,7 @@ export function CompositeRenderer({ data, onNavigate }: CompositeRendererProps)
137
137
  map.set(key, { ref, missing: true })
138
138
  return
139
139
  }
140
- const status = getResourceStatus(kindToPlural(ref.kind), q.data.resource) ?? undefined
140
+ const status = getResourceStatus(kindToPluralWithGroup(ref.kind, groupFromApiVersion(ref.apiVersion)), q.data.resource) ?? undefined
141
141
  map.set(key, {
142
142
  ref,
143
143
  status: status ? { ...status, level: (status as any).level ?? 'unknown' } : undefined,
@@ -1,7 +1,7 @@
1
1
  import { ResourceDetailDrawer as BaseResourceDetailDrawer } from '@skyhook-io/k8s-ui'
2
2
  import type { SelectedResource } from '../../types'
3
3
  import { WorkloadView } from '../workload/WorkloadView'
4
- import { CNPGDrawerTrailBack } from '../cnpg/CNPGDrawerTrail'
4
+ import { ResourceDrawerNavigation } from '../../integrations/resourceHosts'
5
5
 
6
6
  interface ResourceDetailDrawerProps {
7
7
  resource: SelectedResource
@@ -33,7 +33,7 @@ export function ResourceDetailDrawer(props: ResourceDetailDrawerProps) {
33
33
  <BaseResourceDetailDrawer {...props}>
34
34
  {({ resource, expanded, active, initialTab, onClose, onExpand, onExpandIntent, onCancelExpandIntent, onBack, onNavigateToResource, onCollapseToDrawer }) => (
35
35
  <div className="flex h-full min-h-0 flex-col">
36
- {!expanded && <CNPGDrawerTrailBack resource={resource} />}
36
+ {!expanded && <ResourceDrawerNavigation resource={resource} />}
37
37
  <div className="min-h-0 flex-1">
38
38
  <WorkloadView
39
39
  kind={resource.kind}
@@ -1,4 +1,4 @@
1
- import { useState, useMemo, useCallback, useEffect } from 'react'
1
+ import { useState, useMemo, useCallback } from 'react'
2
2
  import { useLocation, useNavigate } from 'react-router-dom'
3
3
  import { useQuery } from '@tanstack/react-query'
4
4
  import { ApiError, debugNamespaceLog, fetchJSON, isForbiddenError, isKindSyncFailed, isStillLoadingError, useCapabilities, useNamespaceCapabilities, useResources, useSecretCertExpiry, useTopPodMetrics, useTopNodeMetrics, useBulkDeleteResources, useBulkRestartWorkloads, useBulkScaleWorkloads, useAudit } from '../../api/client'
@@ -8,10 +8,11 @@ import { apiUrl, getAuthHeaders, getCredentialsMode, stripBasename } from '../..
8
8
  import { readErrorBody } from '../../api/httpErrors'
9
9
  import { useAPIResources } from '../../api/apiResources'
10
10
  import { useConnection } from '../../context/ConnectionContext'
11
- import { initNavigationMap, getSecretStoreProviderType } from '@skyhook-io/k8s-ui'
11
+ import { getSecretStoreProviderType } from '@skyhook-io/k8s-ui'
12
12
  import { usePinnedKinds } from '../../hooks/useFavorites'
13
13
  import { useResourceCounts } from '../../hooks/useResourceCounts'
14
14
  import { useCNPGSidebarWorkspace } from '../cnpg/useCNPGSidebarWorkspace'
15
+ import { renderResourceKindList, resourceKindListMode, useResourceHostFeatures } from '../../integrations/resourceHosts'
15
16
  import { useOpenLogs, useOpenWorkloadLogs } from '../dock'
16
17
  import {
17
18
  canBulkRestartKind,
@@ -35,6 +36,9 @@ interface ResourcesViewProps {
35
36
  onResourceClickYaml?: NavigateToResource
36
37
  onKindChange?: () => void
37
38
  onClearNamespaces?: () => void
39
+ rememberedKindSearch?: (kind: { name: string; group: string }) => string | undefined
40
+ onKindFiltersChange?: (kind: { name: string; group: string }, search: string) => void
41
+ onApplyNamespacesToAllSections?: (namespaces: string[]) => Promise<unknown> | void
38
42
  }
39
43
 
40
44
  type SelectedKindInfo = { name: string; kind: string; group: string } | null
@@ -70,12 +74,13 @@ function hasResourceCount(counts: Record<string, number> | undefined, key: strin
70
74
  return Object.prototype.hasOwnProperty.call(counts ?? {}, key)
71
75
  }
72
76
 
73
- export function ResourcesView({ namespaces, selectedResource, onResourceClick, onResourceClickYaml, onKindChange, onClearNamespaces }: ResourcesViewProps) {
77
+ export function ResourcesView({ namespaces, selectedResource, onResourceClick, onResourceClickYaml, onKindChange, onClearNamespaces, rememberedKindSearch, onKindFiltersChange, onApplyNamespacesToAllSections }: ResourcesViewProps) {
74
78
  const location = useLocation()
75
79
  const navigate = useNavigate()
76
80
  const { connection } = useConnection()
77
81
 
78
- const { data: capabilities } = useCapabilities()
82
+ const { data: capabilities, isPending: capabilitiesPending } = useCapabilities()
83
+ const hostFeatures = useResourceHostFeatures()
79
84
  const namespaceForCapabilities = namespaces.length === 1 ? namespaces[0] : undefined
80
85
  const { data: namespaceCapabilities } = useNamespaceCapabilities(namespaceForCapabilities, capabilities)
81
86
  const namespaceCapabilityNames = useMemo(() => namespaces.length > 1 ? [...namespaces].sort() : [], [namespaces])
@@ -104,15 +109,9 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
104
109
  // API resources discovery
105
110
  const { data: apiResources } = useAPIResources()
106
111
 
107
- // Initialize navigation kind↔plural maps from discovered API resources
108
- useEffect(() => {
109
- if (apiResources) initNavigationMap(apiResources)
110
- }, [apiResources])
111
-
112
- const cnpgSidebarWorkspace = useCNPGSidebarWorkspace({ apiResources, namespaces })
113
-
114
112
  // Track the selected kind from the k8s-ui component
115
113
  const [selectedKind, setSelectedKind] = useState<SelectedKindInfo>(null)
114
+ const cnpgSidebarWorkspace = useCNPGSidebarWorkspace({ apiResources, namespaces, withMetrics: selectedKind?.group === 'postgresql.cnpg.io' })
116
115
  const workloadWrites = namespaces.length === 0
117
116
  ? capabilities?.workloadWrites
118
117
  : namespaces.length === 1
@@ -281,7 +280,7 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
281
280
  printerTable: sanitizePrinterTable(body),
282
281
  }
283
282
  },
284
- enabled: !!selectedKind && !selectedKindQueryBlocked,
283
+ enabled: !!selectedKind && !selectedKindQueryBlocked && resourceKindListMode(selectedKind, hostFeatures, capabilitiesPending) === 'table',
285
284
  staleTime: 30000,
286
285
  refetchInterval: 120000, // Safety net — SSE k8s_event drives near-real-time invalidation
287
286
  retry: (failureCount: number, error: Error) => {
@@ -385,6 +384,17 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
385
384
  setCreateDialogOpen(true)
386
385
  }, [])
387
386
 
387
+ const renderKindView = useCallback(
388
+ (kind: SelectedKindInfo) => renderResourceKindList(kind, hostFeatures, capabilitiesPending, {
389
+ namespaces,
390
+ inspected: selectedResource ?? null,
391
+ onInspect: (resource) => onResourceClick?.(resource),
392
+ onClearNamespaces: onClearNamespaces ?? (() => {}),
393
+ onCreate: handleCreateResource,
394
+ }),
395
+ [hostFeatures, capabilitiesPending, namespaces, selectedResource, onResourceClick, onClearNamespaces, handleCreateResource],
396
+ )
397
+
388
398
  return (
389
399
  <>
390
400
  <BaseResourcesView
@@ -395,6 +405,9 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
395
405
  onResourceClickYaml={onResourceClickYaml}
396
406
  onKindChange={onKindChange}
397
407
  onClearNamespaces={onClearNamespaces}
408
+ rememberedKindSearch={rememberedKindSearch}
409
+ onKindFiltersChange={onKindFiltersChange}
410
+ onApplyNamespacesToAllSections={onApplyNamespacesToAllSections}
398
411
  // Injected data
399
412
  apiResources={apiResources}
400
413
  // Lightweight counts for sidebar (replaces 233 parallel queries)
@@ -434,6 +447,7 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
434
447
  onOpenWorkloadLogs={openWorkloadLogs}
435
448
  // Create resource
436
449
  onCreateResource={handleCreateResource}
450
+ renderKindView={renderKindView}
437
451
  // Bulk operations
438
452
  onBulkDelete={(items, options) => bulkDeleteMutation.mutate({ items, force: options?.force }, { onSuccess: options?.onSuccess })}
439
453
  isBulkDeleting={bulkDeleteMutation.isPending}
@@ -7,6 +7,7 @@ import { formatAge } from '@skyhook-io/k8s-ui/components/resources/resource-util
7
7
  import { isForbiddenError } from '../../../api/client'
8
8
  import { isKyvernoMutateExistingEnabled } from '@skyhook-io/k8s-ui/components/resources/resource-utils-kyverno-modern'
9
9
  import { usePolicyQueued } from '../../../api/policy'
10
+ import { TimeValue } from '@skyhook-io/k8s-ui/components/ui/ScheduleValue'
10
11
 
11
12
  /** A request sitting this long is not mid-flight. Kyverno retries on every
12
13
  * reconcile, so past this point a backlog grows rather than drains. */
@@ -160,7 +161,7 @@ export function KyvernoPolicyQueued({ data }: { data: any }) {
160
161
  />
161
162
  ))}
162
163
  {oldestPending && (
163
- <Property label="Oldest Pending" value={`${formatAge(oldestPending)} ago`} />
164
+ <Property label="Oldest Pending" value={<TimeValue timestamp={oldestPending} />} />
164
165
  )}
165
166
  </PropertyList>
166
167
  {messages.length > 0 && (
@@ -1,6 +1,7 @@
1
1
  import { NodeRenderer as BaseNodeRenderer } from '@skyhook-io/k8s-ui/components/resources/renderers/NodeRenderer'
2
2
  import { useNavigate } from 'react-router-dom'
3
- import { getVisibleLiveMetrics, isLiveMetricsUnavailable, shouldFetchLiveMetrics, useNodeMetrics, useNodeMetricsHistory, usePrometheusResourceMetrics, usePrometheusStatus } from '../../../api/client'
3
+ import { getVisibleLiveMetrics, isLiveMetricsUnavailable, shouldFetchLiveMetrics, useNodeRemovalPlan, useNodeMetrics, useNodeMetricsHistory, usePrometheusResourceMetrics, usePrometheusStatus } from '../../../api/client'
4
+ import { getNodeLifecycle } from '@skyhook-io/k8s-ui/utils/node-lifecycle'
4
5
  import { serializeColumnFilters } from '../resource-utils'
5
6
 
6
7
  interface NodeRendererProps {
@@ -11,6 +12,8 @@ interface NodeRendererProps {
11
12
  export function NodeRenderer({ data, relationships }: NodeRendererProps) {
12
13
  const navigate = useNavigate()
13
14
  const nodeName = data.metadata?.name
15
+ const lifecycle = getNodeLifecycle(data)
16
+ const removalPlan = useNodeRemovalPlan(nodeName, lifecycle.removing)
14
17
 
15
18
  // Fetch node metrics
16
19
  const metricsHistoryQuery = useNodeMetricsHistory(nodeName)
@@ -35,6 +38,9 @@ export function NodeRenderer({ data, relationships }: NodeRendererProps) {
35
38
  return (
36
39
  <BaseNodeRenderer
37
40
  data={data}
41
+ removalPlan={removalPlan.data}
42
+ removalPlanLoading={removalPlan.isLoading}
43
+ removalPlanError={removalPlan.error?.message}
38
44
  relationships={relationships}
39
45
  onViewPods={nodeName ? () => {
40
46
  const params = new URLSearchParams()
@@ -0,0 +1,81 @@
1
+ import { renderToStaticMarkup } from 'react-dom/server'
2
+ import { beforeEach, expect, it, vi } from 'vitest'
3
+ import { RadarFeatureUnsupportedError } from '../../../api/radarFeatures'
4
+ import { ServiceRenderer } from './ServiceRenderer'
5
+
6
+ const mock = vi.hoisted(() => ({
7
+ serviceQuery: {} as any,
8
+ namespaceQuery: {} as any,
9
+ serviceReads: [] as any[],
10
+ namespaceReads: [] as any[],
11
+ }))
12
+ vi.mock('../../../api/client', () => ({
13
+ useServiceEndpointSlices: (...args: any[]) => { mock.serviceReads.push(args); return mock.serviceQuery },
14
+ useResources: (...args: any[]) => { mock.namespaceReads.push(args); return mock.namespaceQuery },
15
+ }))
16
+ vi.mock('../../../contexts/CapabilitiesContext', () => ({ useNamespacedCapabilities: () => ({ canPortForward: false }), useIsLocalDeployment: () => true }))
17
+ vi.mock('../../portforward/PortForwardButton', () => ({ PortForwardInlineButton: () => null }))
18
+ vi.mock('../../curl/ServiceCurlButton', () => ({ CurlButton: () => null, CurlPanel: () => null, isHttpishPort: () => false, defaultScheme: () => '', defaultPathForPort: () => '' }))
19
+
20
+ const service = { apiVersion: 'v1', kind: 'Service', metadata: { name: 'web', namespace: 'team' }, spec: { selector: { app: 'web' } } }
21
+ const selectorless = { ...service, spec: {} }
22
+ const slice = (name: string, labels?: Record<string, string>) => ({ apiVersion: 'discovery.k8s.io/v1', kind: 'EndpointSlice', metadata: { namespace: 'team', name, labels } })
23
+ const render = (data: any = service) => renderToStaticMarkup(<ServiceRenderer data={data} onCopy={() => {}} copied={null} />)
24
+ const unsupported = () => new RadarFeatureUnsupportedError('serviceEndpointSlices', { currentVersion: 'v1.7.0' })
25
+
26
+ beforeEach(() => {
27
+ mock.serviceQuery = {}
28
+ mock.namespaceQuery = {}
29
+ mock.serviceReads = []
30
+ mock.namespaceReads = []
31
+ })
32
+
33
+ it('renders the server-selected slices for a selector Service without listing the namespace', () => {
34
+ mock.serviceQuery = { data: { items: [slice('web-abc', { 'kubernetes.io/service-name': 'web' })], truncated: false } }
35
+ const html = render()
36
+ expect(mock.serviceReads[0]).toEqual(['team', 'web', true])
37
+ expect(mock.namespaceReads[0][3].enabled).toBe(false)
38
+ expect(html).toContain('EndpointSlices')
39
+ expect(html).toContain('web-abc')
40
+ expect(html).not.toContain('Showing the first')
41
+ })
42
+
43
+ it('says when the server cut the inventory off', () => {
44
+ mock.serviceQuery = { data: { items: [slice('web-abc')], truncated: true } }
45
+ expect(render()).toContain('Showing the first 1 EndpointSlices.')
46
+ })
47
+
48
+ it('surfaces a failed lookup instead of an empty inventory', () => {
49
+ mock.serviceQuery = { error: new Error('insufficient permissions to list endpointslices') }
50
+ const html = render()
51
+ expect(html).toContain('EndpointSlice inventory unavailable')
52
+ expect(html).toContain('insufficient permissions to list endpointslices')
53
+ expect(html).not.toContain('No EndpointSlices found')
54
+ })
55
+
56
+ it('does not query slices for ExternalName Services', () => {
57
+ const html = render({ ...service, spec: { type: 'ExternalName', externalName: 'example.org' } })
58
+ expect(mock.serviceReads[0][2]).toBe(false)
59
+ expect(mock.namespaceReads[0][3].enabled).toBe(false)
60
+ expect(html).not.toContain('EndpointSlices')
61
+ })
62
+
63
+ it('falls back to the label-filtered namespace list for selector-less Services on an older Radar', () => {
64
+ mock.serviceQuery = { error: unsupported() }
65
+ mock.namespaceQuery = { data: [
66
+ slice('manual-abc', { 'kubernetes.io/service-name': 'web' }),
67
+ slice('other-abc', { 'kubernetes.io/service-name': 'other' }),
68
+ ] }
69
+ const html = render(selectorless)
70
+ expect(mock.namespaceReads[0]).toEqual(['endpointslices', 'team', 'discovery.k8s.io', { enabled: true, refetchInterval: 30000 }])
71
+ expect(html).toContain('manual-abc')
72
+ expect(html).not.toContain('other-abc')
73
+ expect(html).not.toContain('EndpointSlice inventory unavailable')
74
+ })
75
+
76
+ it('keeps selector Services free of slice lookups on an older Radar', () => {
77
+ mock.serviceQuery = { error: unsupported() }
78
+ const html = render()
79
+ expect(mock.namespaceReads[0][3].enabled).toBe(false)
80
+ expect(html).not.toContain('EndpointSlices')
81
+ })
@@ -2,7 +2,8 @@ import { useMemo, useState, useCallback } from 'react'
2
2
  import { ServiceRenderer as BaseServiceRenderer } from '@skyhook-io/k8s-ui/components/resources/renderers/ServiceRenderer'
3
3
  import { PortForwardInlineButton } from '../../portforward/PortForwardButton'
4
4
  import { CurlButton, CurlPanel, isHttpishPort, defaultScheme, defaultPathForPort } from '../../curl/ServiceCurlButton'
5
- import { useResources } from '../../../api/client'
5
+ import { useResources, useServiceEndpointSlices } from '../../../api/client'
6
+ import { isRadarFeatureUnsupported } from '../../../api/radarFeatures'
6
7
  import { useNamespacedCapabilities, useIsLocalDeployment } from '../../../contexts/CapabilitiesContext'
7
8
  import type { ResourceRef } from '../../../types'
8
9
  import { DURATION_DISCLOSURE } from '@skyhook-io/k8s-ui/utils/animation'
@@ -36,21 +37,22 @@ export function ServiceRenderer({ data, onCopy, copied, onNavigate }: ServiceRen
36
37
  window.setTimeout(() => setCurl((p) => (p?.closing ? null : p)), DURATION_DISCLOSURE + 20)
37
38
  }, [])
38
39
  const spec = data.spec || {}
39
- const shouldLoadEndpointSlices = Boolean(
40
- namespace &&
41
- serviceName &&
42
- spec.type !== 'ExternalName' &&
43
- (!spec.selector || Object.keys(spec.selector).length === 0)
44
- )
45
- const { data: endpointSlices, isLoading: endpointSlicesLoading } = useResources<any>(
40
+ const hasNoSelector = !spec.selector || Object.keys(spec.selector).length === 0
41
+ const canListEndpointSlices = Boolean(namespace && serviceName && spec.type !== 'ExternalName')
42
+ const serviceSlices = useServiceEndpointSlices(namespace, serviceName, canListEndpointSlices)
43
+ // A Radar without the per-Service endpoint keeps its earlier behavior: only
44
+ // selector-less Services list the namespace's slices and filter them here.
45
+ const legacy = isRadarFeatureUnsupported(serviceSlices.error, 'serviceEndpointSlices')
46
+ const shouldLoadNamespaceSlices = canListEndpointSlices && hasNoSelector && legacy
47
+ const { data: namespaceSlices, isLoading: namespaceSlicesLoading, error: namespaceSlicesError } = useResources<any>(
46
48
  'endpointslices',
47
49
  namespace,
48
50
  'discovery.k8s.io',
49
- { enabled: shouldLoadEndpointSlices, refetchInterval: 30000 }
51
+ { enabled: shouldLoadNamespaceSlices, refetchInterval: 30000 }
50
52
  )
51
- const matchingEndpointSlices = useMemo(
52
- () => (endpointSlices || []).filter((slice: any) => slice.metadata?.labels?.['kubernetes.io/service-name'] === serviceName),
53
- [endpointSlices, serviceName]
53
+ const matchingNamespaceSlices = useMemo(
54
+ () => (namespaceSlices || []).filter((slice: any) => slice.metadata?.labels?.['kubernetes.io/service-name'] === serviceName),
55
+ [namespaceSlices, serviceName]
54
56
  )
55
57
 
56
58
  return (
@@ -58,8 +60,11 @@ export function ServiceRenderer({ data, onCopy, copied, onNavigate }: ServiceRen
58
60
  data={data}
59
61
  onCopy={onCopy}
60
62
  copied={copied}
61
- endpointSlices={matchingEndpointSlices}
62
- endpointSlicesLoading={endpointSlicesLoading}
63
+ endpointSlices={legacy ? matchingNamespaceSlices : serviceSlices.data?.items}
64
+ endpointSlicesLoading={legacy ? namespaceSlicesLoading : serviceSlices.isLoading}
65
+ endpointSlicesEnabled={!legacy && canListEndpointSlices}
66
+ endpointSlicesError={(legacy ? namespaceSlicesError : serviceSlices.error)?.message}
67
+ endpointSlicesTruncated={!legacy && serviceSlices.data?.truncated}
63
68
  onNavigate={onNavigate}
64
69
  renderPortAction={({ port, name, appProtocol, protocol }) => (
65
70
  <>
@@ -0,0 +1,52 @@
1
+ import { renderToStaticMarkup } from 'react-dom/server'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ import type { useArgoStatus } from '../../api/client'
4
+ import type { IntegrationProfile } from './LocalConnectionSettings'
5
+ import { LocalIntegrationStatus } from './LocalIntegrationStatus'
6
+
7
+ vi.mock('../../api/client', () => ({
8
+ usePrometheusStatus: () => ({}),
9
+ useOpenCostSummary: () => ({}),
10
+ }))
11
+
12
+ const profile: IntegrationProfile = {
13
+ target: {
14
+ binding: 'opaque',
15
+ context: 'development',
16
+ source: '/configs/team',
17
+ inFileName: 'dev',
18
+ fingerprint: 'target',
19
+ clientGeneration: 1,
20
+ operationGeneration: 1,
21
+ identity: { server: 'https://kubernetes', user: 'developer', trust: 'trust', insecureTls: false },
22
+ },
23
+ revision: 'revision',
24
+ state: 'saved',
25
+ mode: 'auto',
26
+ url: '',
27
+ headerKeys: [],
28
+ envHeaderKeys: [],
29
+ headersManaged: false,
30
+ secretSet: true,
31
+ insecureTls: false,
32
+ clusterId: '',
33
+ }
34
+
35
+ const render = (data: Record<string, unknown>) => {
36
+ const argo = { data, isError: false, isPlaceholderData: false, isFetching: false, dataUpdatedAt: 1, errorUpdatedAt: 0 } as unknown as ReturnType<typeof useArgoStatus>
37
+ return renderToStaticMarkup(<LocalIntegrationStatus kind="argocd" profile={profile} argo={argo} busy={false} changedAt={0} />)
38
+ }
39
+
40
+ describe('LocalIntegrationStatus for Argo CD', () => {
41
+ it('says Connecting while the first connection is being made', () => {
42
+ const html = render({ configured: true, connected: false, connecting: true })
43
+ expect(html).toContain('Connecting…')
44
+ expect(html).not.toContain('Not connected')
45
+ })
46
+
47
+ it('shows why the last read failed on a working connection', () => {
48
+ const html = render({ configured: true, connected: true, address: 'https://argocd', reason: "Radar's last read from Argo CD failed: the request timed out." })
49
+ expect(html).toContain('Connected')
50
+ expect(html).toContain('the request timed out')
51
+ })
52
+ })
@@ -26,13 +26,12 @@ export function LocalIntegrationStatus({ kind, profile, argo, busy, changedAt }:
26
26
  status = prom.data.connected ? 'Connected' : prom.data.discovering ? 'Discovering…' : 'Not connected'
27
27
  detail = prom.data.connected ? !profile.url ? `Discovered: ${prom.data.address}` : undefined : prom.data.error
28
28
  } else if (kind === 'argocd' && argo.data) {
29
- status = argo.data.connected ? argo.data.anonymous ? 'Connected · no token needed' : 'Connected' : 'Not connected'
30
- detail = argo.data.connected ? !profile.url ? `Discovered: ${argo.data.address}` : undefined : argo.data.reason
29
+ status = argo.data.connected ? argo.data.anonymous ? 'Connected · no token needed' : 'Connected' : argo.data.connecting ? 'Connecting…' : 'Not connected'
30
+ detail = argo.data.reason ?? (argo.data.connected && !profile.url ? `Discovered: ${argo.data.address}` : undefined)
31
31
  } else if (kind === 'cost' && cost.data) {
32
32
  status = cost.data.available ? `Available · ${costSourceLabel(cost.data.source)}`
33
33
  : ['no_prometheus', 'no_cost_source', 'no_metrics'].includes(cost.data.reason ?? '') ? 'No cost data' : 'Unavailable'
34
- detail = cost.data.reason ? costIntegrationUnavailableMessage(cost.data.reason)
35
- ?? (cost.data.reason === 'query_error' ? 'The active cost source query failed.' : undefined) : undefined
34
+ detail = cost.data.reason ? costIntegrationUnavailableMessage(cost.data.reason) : undefined
36
35
  }
37
36
  }
38
37
  const lastDetail = useRef({ text: detail, visible: !!detail })