@skyhook-io/k8s-ui 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 (309) hide show
  1. package/README.md +41 -0
  2. package/package.json +3 -3
  3. package/src/components/applications/ApplicationDetail.test.tsx +15 -0
  4. package/src/components/applications/ApplicationDetail.tsx +17 -15
  5. package/src/components/charts/AreaChart.interaction.test.tsx +118 -0
  6. package/src/components/charts/AreaChart.test.tsx +25 -0
  7. package/src/components/charts/AreaChart.tsx +141 -12
  8. package/src/components/charts/index.ts +1 -0
  9. package/src/components/checks/ChecksView.tsx +18 -1
  10. package/src/components/checks/types.ts +2 -13
  11. package/src/components/cnpg/CNPGBackupSummary.tsx +57 -9
  12. package/src/components/cnpg/CNPGClusterHASection.test.tsx +90 -0
  13. package/src/components/cnpg/CNPGClusterHASection.tsx +331 -0
  14. package/src/components/cnpg/CNPGClusterSummary.test.tsx +275 -0
  15. package/src/components/cnpg/CNPGClusterSummary.tsx +310 -96
  16. package/src/components/cnpg/CNPGConnectSection.test.tsx +64 -0
  17. package/src/components/cnpg/CNPGConnectSection.tsx +178 -0
  18. package/src/components/cnpg/CNPGDeclarativeSummary.tsx +157 -26
  19. package/src/components/cnpg/CNPGImageCatalogSummary.tsx +7 -4
  20. package/src/components/cnpg/CNPGLogicalPath.test.tsx +22 -0
  21. package/src/components/cnpg/CNPGLogicalPath.tsx +150 -0
  22. package/src/components/cnpg/CNPGObjectStoreSummary.tsx +9 -6
  23. package/src/components/cnpg/CNPGObjectSummary.test.tsx +193 -2
  24. package/src/components/cnpg/CNPGPoolerScheduling.test.tsx +36 -0
  25. package/src/components/cnpg/CNPGPoolerSummary.tsx +241 -14
  26. package/src/components/cnpg/CNPGSharedSummary.tsx +15 -8
  27. package/src/components/cnpg/CNPGWALArchivingFact.test.tsx +39 -0
  28. package/src/components/cnpg/backupRuns.test.ts +17 -0
  29. package/src/components/cnpg/backupRuns.ts +19 -0
  30. package/src/components/cnpg/connect.test.ts +124 -0
  31. package/src/components/cnpg/connect.ts +155 -0
  32. package/src/components/cnpg/databaseRole.test.ts +38 -0
  33. package/src/components/cnpg/databaseRole.ts +80 -0
  34. package/src/components/cnpg/ha.test.ts +446 -0
  35. package/src/components/cnpg/ha.ts +621 -0
  36. package/src/components/cnpg/index.ts +18 -0
  37. package/src/components/cnpg/logicalReplication.test.ts +121 -0
  38. package/src/components/cnpg/logicalReplication.ts +252 -0
  39. package/src/components/cnpg/pooler.test.ts +84 -0
  40. package/src/components/cnpg/pooler.ts +225 -0
  41. package/src/components/cnpg/primitives.tsx +21 -117
  42. package/src/components/cnpg/problems.ts +12 -0
  43. package/src/components/cnpg/relations.test.ts +63 -13
  44. package/src/components/cnpg/relations.ts +61 -52
  45. package/src/components/cnpg/schedule.ts +35 -0
  46. package/src/components/cnpg/workspace-disk.test.ts +97 -0
  47. package/src/components/cnpg/workspace-fleet-metrics.test.ts +250 -0
  48. package/src/components/cnpg/workspace-problems.test.ts +175 -0
  49. package/src/components/cnpg/workspace.test.ts +380 -9
  50. package/src/components/cnpg/workspace.ts +1137 -136
  51. package/src/components/dock/DockContext.test.tsx +32 -0
  52. package/src/components/dock/DockContext.tsx +18 -4
  53. package/src/components/dock/LocalTerminalTab.test.tsx +40 -0
  54. package/src/components/dock/LocalTerminalTab.tsx +4 -3
  55. package/src/components/dock/TerminalTab.tsx +20 -8
  56. package/src/components/facts/certainty.tsx +36 -0
  57. package/src/components/facts/facts.test.tsx +24 -0
  58. package/src/components/facts/facts.tsx +62 -0
  59. package/src/components/facts/index.ts +6 -0
  60. package/src/components/facts/managed-by.test.tsx +17 -0
  61. package/src/components/facts/managed-by.tsx +36 -0
  62. package/src/components/facts/public-exports.test.tsx +35 -0
  63. package/src/components/gitops/GitOpsGraphFilterRail.tsx +6 -2
  64. package/src/components/gitops/GitOpsHealthSourceNotice.test.tsx +38 -0
  65. package/src/components/gitops/GitOpsHealthSourceNotice.tsx +13 -3
  66. package/src/components/gitops/GitOpsTableView.tsx +9 -24
  67. package/src/components/gitops/detail-helpers.ts +1 -1
  68. package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +115 -0
  69. package/src/components/gitops/insights/ArgoResourceDiff.tsx +22 -5
  70. package/src/components/gitops/insights/DriftPanel.tsx +62 -0
  71. package/src/components/gitops/insights/GitOpsInsightViews.tsx +29 -83
  72. package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
  73. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +40 -60
  74. package/src/components/gitops/tree/layout.test.ts +111 -0
  75. package/src/components/gitops/tree/layout.ts +128 -0
  76. package/src/components/gitops/tree/tree-helpers.test.ts +17 -0
  77. package/src/components/gitops/tree/tree-helpers.ts +7 -1
  78. package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
  79. package/src/components/issues/IssuesView.tsx +40 -5
  80. package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
  81. package/src/components/issues/ResourceIssuesSection.tsx +3 -0
  82. package/src/components/issues/index.ts +3 -1
  83. package/src/components/issues/issue-timing.ts +14 -3
  84. package/src/components/issues/issues.test.ts +50 -2
  85. package/src/components/issues/severity.ts +21 -0
  86. package/src/components/issues/types.ts +23 -0
  87. package/src/components/logs/LogCore.theme.test.tsx +81 -3
  88. package/src/components/logs/LogCore.tsx +25 -17
  89. package/src/components/logs/LogToolbarSelects.tsx +3 -1
  90. package/src/components/logs/LogsViewer.test.tsx +291 -0
  91. package/src/components/logs/LogsViewer.tsx +72 -11
  92. package/src/components/logs/StructuredLogLine.test.tsx +76 -0
  93. package/src/components/logs/StructuredLogLine.tsx +22 -30
  94. package/src/components/logs/WorkloadLogsViewer.notice.test.tsx +59 -0
  95. package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
  96. package/src/components/logs/WorkloadLogsViewer.tsx +44 -10
  97. package/src/components/logs/useLogBuffer.test.ts +8 -0
  98. package/src/components/logs/useLogStream.ts +28 -1
  99. package/src/components/problems/index.ts +1 -0
  100. package/src/components/problems/problems.test.tsx +63 -0
  101. package/src/components/problems/problems.tsx +165 -0
  102. package/src/components/resources/ActiveFilterBar.test.tsx +78 -0
  103. package/src/components/resources/ActiveFilterBar.tsx +86 -0
  104. package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
  105. package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
  106. package/src/components/resources/ResourcesSidebar.tsx +92 -41
  107. package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
  108. package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -0
  109. package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
  110. package/src/components/resources/ResourcesView.tsx +309 -117
  111. package/src/components/resources/curated-column-ownership.test.ts +3 -3
  112. package/src/components/resources/renderers/AnalysisRunRenderer.tsx +3 -3
  113. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +6 -5
  114. package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
  115. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +5 -4
  116. package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
  117. package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +2 -2
  118. package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +2 -2
  119. package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +2 -2
  120. package/src/components/resources/renderers/CAPIMachineRenderer.tsx +2 -2
  121. package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +2 -2
  122. package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
  123. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
  124. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +7 -8
  125. package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
  126. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
  127. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +9 -17
  128. package/src/components/resources/renderers/CNPGShared.tsx +2 -2
  129. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
  130. package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
  131. package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
  132. package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
  133. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +2 -2
  134. package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
  135. package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
  136. package/src/components/resources/renderers/CronJobRenderer.tsx +6 -4
  137. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +3 -2
  138. package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +62 -0
  139. package/src/components/resources/renderers/EndpointSliceRenderer.tsx +15 -14
  140. package/src/components/resources/renderers/EventRenderer.tsx +3 -3
  141. package/src/components/resources/renderers/ExperimentRenderer.tsx +2 -2
  142. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
  143. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
  144. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +4 -4
  145. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +11 -24
  146. package/src/components/resources/renderers/GatewayRouteReferences.test.tsx +63 -0
  147. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -3
  148. package/src/components/resources/renderers/HPARenderer.tsx +57 -17
  149. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +12 -25
  150. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -3
  151. package/src/components/resources/renderers/IngressClassReferences.test.tsx +52 -0
  152. package/src/components/resources/renderers/IngressClassRenderer.tsx +10 -3
  153. package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
  154. package/src/components/resources/renderers/IngressRenderer.tsx +36 -5
  155. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
  156. package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
  157. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
  158. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -14
  159. package/src/components/resources/renderers/KedaCredentials.test.tsx +85 -0
  160. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +15 -13
  161. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +6 -3
  162. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
  163. package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
  164. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
  165. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
  166. package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
  167. package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
  168. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
  169. package/src/components/resources/renderers/KustomizationRenderer.tsx +2 -2
  170. package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +3 -2
  171. package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +4 -3
  172. package/src/components/resources/renderers/LeaseRenderer.tsx +3 -15
  173. package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
  174. package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
  175. package/src/components/resources/renderers/NamespaceRenderer.test.ts +69 -2
  176. package/src/components/resources/renderers/NamespaceRenderer.tsx +6 -1
  177. package/src/components/resources/renderers/NodeRenderer.test.tsx +81 -1
  178. package/src/components/resources/renderers/NodeRenderer.tsx +56 -15
  179. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -3
  180. package/src/components/resources/renderers/PodClassReferences.test.tsx +42 -0
  181. package/src/components/resources/renderers/PodNomination.test.tsx +57 -0
  182. package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
  183. package/src/components/resources/renderers/PodRenderer.tsx +62 -36
  184. package/src/components/resources/renderers/RolloutRenderer.test.tsx +1 -1
  185. package/src/components/resources/renderers/RolloutRenderer.tsx +3 -2
  186. package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
  187. package/src/components/resources/renderers/SecretRenderer.test.tsx +88 -0
  188. package/src/components/resources/renderers/SecretRenderer.tsx +34 -5
  189. package/src/components/resources/renderers/ServiceRenderer.test.tsx +43 -0
  190. package/src/components/resources/renderers/ServiceRenderer.tsx +11 -3
  191. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +9 -22
  192. package/src/components/resources/renderers/VPARenderer.tsx +17 -11
  193. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +5 -7
  194. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +3 -3
  195. package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +2 -2
  196. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +3 -3
  197. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +2 -2
  198. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
  199. package/src/components/resources/renderers/WorkflowRenderer.tsx +4 -3
  200. package/src/components/resources/renderers/kyverno-modern-cells.tsx +1 -1
  201. package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +2 -2
  202. package/src/components/resources/resource-utils-capi.ts +1 -1
  203. package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
  204. package/src/components/resources/resource-utils-cnpg.ts +51 -24
  205. package/src/components/resources/resource-utils-contour.test.ts +44 -0
  206. package/src/components/resources/resource-utils-contour.ts +16 -0
  207. package/src/components/resources/resource-utils-istio.ts +12 -8
  208. package/src/components/resources/resource-utils-keda.ts +1 -7
  209. package/src/components/resources/resource-utils-knative.ts +18 -0
  210. package/src/components/resources/resource-utils-kyverno.ts +1 -7
  211. package/src/components/resources/resource-utils-velero.ts +0 -12
  212. package/src/components/resources/resource-utils.ts +99 -39
  213. package/src/components/resources/rules-column-width.test.ts +1 -1
  214. package/src/components/resources/summarize-scheduler-message.test.ts +30 -0
  215. package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
  216. package/src/components/shared/ActionConfirmDialog.tsx +246 -0
  217. package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
  218. package/src/components/shared/CreateResourceDialog.tsx +126 -80
  219. package/src/components/shared/DetailShell.test.tsx +80 -0
  220. package/src/components/shared/DetailShell.tsx +85 -10
  221. package/src/components/shared/DrainPlanDialog.tsx +1 -0
  222. package/src/components/shared/EditableYamlView.test.tsx +7 -0
  223. package/src/components/shared/EditableYamlView.tsx +1 -1
  224. package/src/components/shared/GitOpsWriteWarning.test.tsx +86 -0
  225. package/src/components/shared/GitOpsWriteWarning.tsx +135 -0
  226. package/src/components/shared/ResourceActionsBar.tsx +20 -24
  227. package/src/components/shared/ResourceRendererDispatch.tsx +13 -10
  228. package/src/components/shared/SetImageDialog.test.ts +75 -6
  229. package/src/components/shared/SetImageDialog.tsx +92 -84
  230. package/src/components/shared/index.ts +4 -1
  231. package/src/components/timeline/TimelineList.tsx +2 -15
  232. package/src/components/timeline/TimelineSwimlanes.tsx +3 -11
  233. package/src/components/timeline/timeline-filters.ts +1 -0
  234. package/src/components/topology/K8sResourceNode.test.ts +18 -1
  235. package/src/components/topology/K8sResourceNode.tsx +44 -2
  236. package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
  237. package/src/components/topology/TopologyGraph.tsx +8 -74
  238. package/src/components/topology/TopologySearch.tsx +1 -0
  239. package/src/components/topology/internet-groups.test.ts +35 -0
  240. package/src/components/topology/internet-groups.ts +55 -0
  241. package/src/components/topology/layout.ts +1 -1
  242. package/src/components/ui/Badge.tsx +1 -0
  243. package/src/components/ui/FoldSection.tsx +68 -0
  244. package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
  245. package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
  246. package/src/components/ui/RefLink.tsx +20 -0
  247. package/src/components/ui/RowActionMenu.tsx +8 -3
  248. package/src/components/ui/ScheduleValue.test.tsx +5 -0
  249. package/src/components/ui/ScheduleValue.tsx +3 -2
  250. package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
  251. package/src/components/ui/drawer-components.test.tsx +175 -1
  252. package/src/components/ui/drawer-components.tsx +103 -36
  253. package/src/components/ui/index.ts +3 -1
  254. package/src/components/ui/monacoEditorFeatures.ts +77 -0
  255. package/src/components/ui/monacoRuntime.ts +14 -19
  256. package/src/components/ui/status-tone.test.ts +10 -1
  257. package/src/components/ui/status-tone.tsx +29 -0
  258. package/src/components/workload/WorkloadView.deleted.test.tsx +101 -0
  259. package/src/components/workload/WorkloadView.deletedDialog.test.tsx +45 -0
  260. package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
  261. package/src/components/workload/WorkloadView.secretSave.test.tsx +62 -0
  262. package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
  263. package/src/components/workload/WorkloadView.tsx +297 -104
  264. package/src/index.ts +14 -1
  265. package/src/monaco-deep.d.ts +19 -12
  266. package/src/theme/components.css +13 -0
  267. package/src/types/capacity.ts +21 -2
  268. package/src/types/core.ts +60 -2
  269. package/src/types/gitops-flux-status.test.ts +32 -1
  270. package/src/types/gitops-insights.ts +19 -2
  271. package/src/types/gitops-tree.ts +10 -0
  272. package/src/types/gitops.ts +26 -6
  273. package/src/utils/api-resources.ts +7 -0
  274. package/src/utils/application-history.ts +1 -0
  275. package/src/utils/cnpg-backup.test.ts +26 -0
  276. package/src/utils/cnpg-backup.ts +70 -0
  277. package/src/utils/format.test.ts +49 -2
  278. package/src/utils/format.ts +9 -1
  279. package/src/utils/gateway-references.test.ts +17 -0
  280. package/src/utils/gateway-references.ts +29 -0
  281. package/src/utils/gitops-owner.ts +3 -2
  282. package/src/utils/gitops-write-guard.test.ts +401 -0
  283. package/src/utils/gitops-write-guard.ts +509 -0
  284. package/src/utils/grant.test.ts +19 -0
  285. package/src/utils/grant.ts +35 -0
  286. package/src/utils/index.ts +6 -0
  287. package/src/utils/ingress-class-references.ts +22 -0
  288. package/src/utils/k8s-errors.ts +7 -7
  289. package/src/utils/log-format.test.ts +41 -0
  290. package/src/utils/log-format.ts +71 -15
  291. package/src/utils/log-level.ts +12 -8
  292. package/src/utils/navigation.test.ts +70 -0
  293. package/src/utils/navigation.ts +91 -8
  294. package/src/utils/node-lifecycle.test.ts +36 -0
  295. package/src/utils/node-lifecycle.ts +77 -0
  296. package/src/utils/object-reference-navigation.test.ts +43 -0
  297. package/src/utils/resource-hierarchy.test.ts +69 -0
  298. package/src/utils/resource-hierarchy.ts +41 -7
  299. package/src/utils/resource-icons.ts +2 -0
  300. package/src/utils/resource-refs.test.ts +23 -0
  301. package/src/utils/resource-refs.ts +30 -0
  302. package/src/utils/skeleton-yaml.test.ts +21 -0
  303. package/src/utils/skeleton-yaml.ts +16 -0
  304. package/src/utils/url-path.test.ts +35 -0
  305. package/src/utils/url-path.ts +21 -0
  306. package/src/utils/validators.test.ts +4 -13
  307. package/src/utils/validators.ts +5 -19
  308. package/src/utils/view-transition.test.ts +35 -1
  309. package/src/utils/view-transition.ts +16 -1
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { renderToString } from 'react-dom/server'
3
+ import { IngressRenderer } from './IngressRenderer'
4
+
5
+ describe('IngressRenderer default backend', () => {
6
+ it('shows the default backend and no "not routed" warning', () => {
7
+ const html = renderToString(
8
+ <IngressRenderer data={{
9
+ metadata: { name: 'catch-all', namespace: 'team' },
10
+ spec: { ingressClassName: 'nginx', defaultBackend: { service: { name: 'fallback', port: { number: 80 } } } },
11
+ }} />,
12
+ )
13
+ expect(html).toContain('Default Backend')
14
+ expect(html.replace(/<!-- -->/g, '')).toContain('fallback:80')
15
+ expect(html).not.toContain('Traffic will not be routed')
16
+ })
17
+
18
+ it('still warns when an Ingress routes nowhere', () => {
19
+ const html = renderToString(<IngressRenderer data={{ metadata: { name: 'empty', namespace: 'team' }, spec: { ingressClassName: 'nginx' } }} />)
20
+ expect(html).toContain('Traffic will not be routed')
21
+ })
22
+
23
+ it('shows a resource default backend', () => {
24
+ const html = renderToString(
25
+ <IngressRenderer data={{
26
+ metadata: { name: 'static', namespace: 'team' },
27
+ spec: { ingressClassName: 'nginx', defaultBackend: { resource: { apiGroup: 'k8s.example.com', kind: 'StorageBucket', name: 'static-assets' } } },
28
+ }} />,
29
+ )
30
+ expect(html).toContain('Default Backend')
31
+ expect(html).toContain('StorageBucket/static-assets')
32
+ })
33
+ })
@@ -1,9 +1,11 @@
1
1
  import { Globe, Shield, Clock } from 'lucide-react'
2
+ import type { ResourceRef } from '../../../types'
2
3
  import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
4
+ import { kindToPluralWithGroup } from '../../../utils/navigation'
3
5
 
4
6
  interface IngressRendererProps {
5
7
  data: any
6
- onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
8
+ onNavigate?: (ref: ResourceRef) => void
7
9
  }
8
10
 
9
11
  export function IngressRenderer({ data, onNavigate }: IngressRendererProps) {
@@ -15,7 +17,10 @@ export function IngressRenderer({ data, onNavigate }: IngressRendererProps) {
15
17
  // Check for issues
16
18
  const hasNoAddress = lbIngress.length === 0
17
19
  const hasNoClass = !spec.ingressClassName && !data.metadata?.annotations?.['kubernetes.io/ingress.class']
18
- const hasNoRules = rules.length === 0
20
+ const defaultService = spec.defaultBackend?.service
21
+ const defaultResource = spec.defaultBackend?.resource
22
+ // An Ingress with only a default backend still routes everything to it.
23
+ const hasNoRules = rules.length === 0 && !spec.defaultBackend
19
24
 
20
25
  return (
21
26
  <>
@@ -42,15 +47,41 @@ export function IngressRenderer({ data, onNavigate }: IngressRendererProps) {
42
47
 
43
48
  <Section title="Ingress" icon={Globe}>
44
49
  <PropertyList>
45
- <Property label="Class" value={spec.ingressClassName || data.metadata?.annotations?.['kubernetes.io/ingress.class']} />
50
+ <Property label="Class" value={spec.ingressClassName
51
+ ? <ResourceLink name={spec.ingressClassName} kind="ingressclasses" group="networking.k8s.io" onNavigate={onNavigate} />
52
+ : data.metadata?.annotations?.['kubernetes.io/ingress.class']
53
+ } />
46
54
  {lbIngress.length > 0 && (
47
55
  <Property label="Address" value={lbIngress[0].ip || lbIngress[0].hostname} />
48
56
  )}
57
+ {defaultService?.name && (
58
+ <Property label="Default Backend" value={
59
+ <ResourceLink
60
+ name={defaultService.name}
61
+ kind="services"
62
+ namespace={data.metadata?.namespace || ''}
63
+ label={<span className="text-blue-400">{defaultService.name}:{defaultService.port?.number || defaultService.port?.name}</span>}
64
+ onNavigate={onNavigate}
65
+ />
66
+ } />
67
+ )}
68
+ {defaultResource?.name && (
69
+ <Property label="Default Backend" value={
70
+ <ResourceLink
71
+ name={defaultResource.name}
72
+ kind={kindToPluralWithGroup(defaultResource.kind, defaultResource.apiGroup || '')}
73
+ group={defaultResource.apiGroup || ''}
74
+ namespace={data.metadata?.namespace || ''}
75
+ label={<span className="text-blue-400">{`${defaultResource.kind}/${defaultResource.name}`}</span>}
76
+ onNavigate={onNavigate}
77
+ />
78
+ } />
79
+ )}
49
80
  <Property label="TLS" value={tls.length > 0 ? `${tls.length} certificate(s)` : 'None'} />
50
81
  </PropertyList>
51
82
  </Section>
52
83
 
53
- <Section title="Rules" defaultExpanded>
84
+ {rules.length > 0 && <Section title="Rules" defaultExpanded>
54
85
  <div className="space-y-3">
55
86
  {rules.map((rule: any, i: number) => (
56
87
  <div key={i} className="card-inner-lg">
@@ -83,7 +114,7 @@ export function IngressRenderer({ data, onNavigate }: IngressRendererProps) {
83
114
  </div>
84
115
  ))}
85
116
  </div>
86
- </Section>
117
+ </Section>}
87
118
 
88
119
  {tls.length > 0 && (
89
120
  <Section title="TLS" icon={Shield}>
@@ -2,6 +2,7 @@ import { Network } from 'lucide-react'
2
2
  import { Section, PropertyList, Property, ConditionsSection, ResourceLink, KeyValueBadgeList } from '../../ui/drawer-components'
3
3
  import {
4
4
  getDestinationRuleHost,
5
+ resolveIstioServiceHost,
5
6
  getDestinationRuleSubsets,
6
7
  getDestinationRuleTrafficPolicy,
7
8
  getDestinationRuleLoadBalancer,
@@ -9,20 +10,18 @@ import {
9
10
 
10
11
  interface IstioDestinationRuleRendererProps {
11
12
  data: any
13
+ serviceDomain?: string
12
14
  onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
13
15
  }
14
16
 
15
- export function IstioDestinationRuleRenderer({ data, onNavigate }: IstioDestinationRuleRendererProps) {
17
+ export function IstioDestinationRuleRenderer({ data, onNavigate, serviceDomain }: IstioDestinationRuleRendererProps) {
16
18
  const host = getDestinationRuleHost(data)
17
19
  const subsets = getDestinationRuleSubsets(data)
18
20
  const trafficPolicy = getDestinationRuleTrafficPolicy(data)
19
21
  const loadBalancer = getDestinationRuleLoadBalancer(data)
20
22
  const ns = data.metadata?.namespace || ''
21
23
 
22
- // Parse host to create service link
23
- const hostParts = host.split('.')
24
- const svcName = hostParts[0]
25
- const svcNs = hostParts.length >= 2 ? hostParts[1] : ns
24
+ const service = resolveIstioServiceHost(host, ns, serviceDomain)
26
25
 
27
26
  return (
28
27
  <>
@@ -30,15 +29,16 @@ export function IstioDestinationRuleRenderer({ data, onNavigate }: IstioDestinat
30
29
  <Section title="Destination" icon={Network} defaultExpanded>
31
30
  <PropertyList>
32
31
  <Property label="Host" value={
33
- host !== '-' ? (
32
+ service ? (
34
33
  <ResourceLink
35
- name={svcName}
34
+ name={service.name}
36
35
  kind="services"
37
- namespace={svcNs}
36
+ group=""
37
+ namespace={service.namespace}
38
38
  label={host}
39
39
  onNavigate={onNavigate}
40
40
  />
41
- ) : '-'
41
+ ) : host
42
42
  } />
43
43
  <Property label="Load Balancer" value={loadBalancer} />
44
44
  </PropertyList>
@@ -0,0 +1,34 @@
1
+ // @vitest-environment jsdom
2
+ import { act, type ReactNode } from 'react'
3
+ import { createRoot } from 'react-dom/client'
4
+ import { describe, expect, it, vi } from 'vitest'
5
+ import { IstioDestinationRuleRenderer } from './IstioDestinationRuleRenderer'
6
+
7
+ async function rendered(element: ReactNode, check: (container: HTMLDivElement) => Promise<void> | void) {
8
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
9
+ const container = document.createElement('div')
10
+ const root = createRoot(container)
11
+ try { await act(async () => root.render(element)); await check(container) }
12
+ finally { await act(async () => root.unmount()); Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false }) }
13
+ }
14
+ async function click(container: HTMLDivElement, text: string) {
15
+ const button = [...container.querySelectorAll('button')].find(button => button.textContent === text)
16
+ expect(button, `missing link ${text}`).toBeDefined()
17
+ await act(async () => button!.click())
18
+ }
19
+
20
+ describe('exact reference navigation', () => {
21
+ it.each([
22
+ {host:'api.prod.svc.mesh.internal',domain:'mesh.internal',linked:true},
23
+ {host:'api.prod.svc.cluster.local',domain:undefined,linked:false},
24
+ {host:'api.prod.svc.external.test',domain:'mesh.internal',linked:false},
25
+ {host:'api.prod',domain:'mesh.internal',linked:false},
26
+ ])('links only observed Istio service domains: $host',async ({host,domain,linked})=>{
27
+ const navigate=vi.fn()
28
+ await rendered(<IstioDestinationRuleRenderer data={{metadata:{namespace:'app'},spec:{host}}} serviceDomain={domain} onNavigate={navigate}/>,async container=>{
29
+ const link=[...container.querySelectorAll('button')].find(button=>button.textContent===host)
30
+ expect(Boolean(link)).toBe(linked)
31
+ if(link) {await click(container,host);expect(navigate).toHaveBeenCalledWith({kind:'services',group:'',namespace:'prod',name:'api'})}
32
+ })
33
+ })
34
+ })
@@ -7,6 +7,7 @@ import {
7
7
  getVirtualServiceHostsList,
8
8
  getVirtualServiceGatewaysList,
9
9
  getVirtualServiceHttpRoutes,
10
+ resolveIstioServiceHost,
10
11
  getVirtualServiceTcpRoutes,
11
12
  getVirtualServiceTlsRoutes,
12
13
  getVirtualServiceRouteCount,
@@ -14,10 +15,11 @@ import {
14
15
 
15
16
  interface IstioVirtualServiceRendererProps {
16
17
  data: any
18
+ serviceDomain?: string
17
19
  onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
18
20
  }
19
21
 
20
- export function IstioVirtualServiceRenderer({ data, onNavigate }: IstioVirtualServiceRendererProps) {
22
+ export function IstioVirtualServiceRenderer({ data, onNavigate, serviceDomain }: IstioVirtualServiceRendererProps) {
21
23
  const status = getVirtualServiceStatus(data)
22
24
  const hosts = getVirtualServiceHostsList(data)
23
25
  const gateways = getVirtualServiceGatewaysList(data)
@@ -148,6 +150,7 @@ export function IstioVirtualServiceRenderer({ data, onNavigate }: IstioVirtualSe
148
150
  {route.route.map((dest: any, di: number) => {
149
151
  const weight = dest.weight ?? (route.route.length === 1 ? 100 : undefined)
150
152
  const destHost = dest.destination?.host || ''
153
+ const service = resolveIstioServiceHost(destHost, ns, serviceDomain)
151
154
  const destPort = dest.destination?.port?.number
152
155
  const destSubset = dest.destination?.subset
153
156
 
@@ -164,13 +167,7 @@ export function IstioVirtualServiceRenderer({ data, onNavigate }: IstioVirtualSe
164
167
  <span className="text-theme-text-secondary font-medium">{weight}%</span>
165
168
  </div>
166
169
  )}
167
- <ResourceLink
168
- name={destHost.split('.')[0]}
169
- kind="services"
170
- namespace={destHost.includes('.') ? destHost.split('.')[1] : ns}
171
- label={destHost}
172
- onNavigate={onNavigate}
173
- />
170
+ {service ? <ResourceLink name={service.name} kind="services" group="" namespace={service.namespace} label={destHost} onNavigate={onNavigate} /> : <span className="text-theme-text-primary break-all">{destHost}</span>}
174
171
  {destPort && (
175
172
  <span className="text-theme-text-tertiary">:{destPort}</span>
176
173
  )}
@@ -3,6 +3,7 @@ import { clsx } from 'clsx'
3
3
  import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown } from '../../ui/drawer-components'
4
4
  import { kindToPlural } from '../../../utils/navigation'
5
5
  import { CAPACITY_TYPE_BADGE, BADGE_INACTIVE } from '../../../utils/badge-colors'
6
+ import { formatRelativeAgeTime } from '../../../utils/format'
6
7
  import {
7
8
  getNodeClaimStatus,
8
9
  getNodeClaimInstanceType,
@@ -14,19 +15,6 @@ import {
14
15
  getNodeClaimExpireAfter,
15
16
  } from '../resource-utils-karpenter'
16
17
 
17
- function formatRelativeTime(isoString: string): string {
18
- const date = new Date(isoString)
19
- if (isNaN(date.getTime())) return ''
20
- const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
21
- if (seconds < 60) return `${seconds}s ago`
22
- const minutes = Math.floor(seconds / 60)
23
- if (minutes < 60) return `${minutes}m ago`
24
- const hours = Math.floor(minutes / 60)
25
- if (hours < 24) return `${hours}h ago`
26
- const days = Math.floor(hours / 24)
27
- return `${days}d ago`
28
- }
29
-
30
18
  interface KarpenterNodeClaimRendererProps {
31
19
  data: any
32
20
  onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
@@ -205,7 +193,7 @@ export function KarpenterNodeClaimRenderer({ data, onNavigate }: KarpenterNodeCl
205
193
  </span>
206
194
  {transitionTime && (
207
195
  <span className="text-xs text-theme-text-tertiary shrink-0">
208
- {formatRelativeTime(transitionTime)}
196
+ {formatRelativeAgeTime(transitionTime, '')}
209
197
  </span>
210
198
  )}
211
199
  </div>
@@ -0,0 +1,85 @@
1
+ // @vitest-environment jsdom
2
+ import { act } from 'react'
3
+ import { createRoot } from 'react-dom/client'
4
+ import { renderToStaticMarkup } from 'react-dom/server'
5
+ import { expect, it, vi } from 'vitest'
6
+ import { KedaTriggerAuthRenderer } from './KedaTriggerAuthRenderer'
7
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
8
+ const spec = {
9
+ secretTargetRef: [{ parameter: 'token', name: 'direct', key: 'token' }],
10
+ gcpSecretManager: {
11
+ credentials: {
12
+ clientSecret: {
13
+ valueFrom: { secretKeyRef: { name: 'iam', key: 'credentials.json' } },
14
+ },
15
+ },
16
+ },
17
+ }
18
+
19
+ it('links the actual nested GCP credential Secret in the authentication namespace', async () => {
20
+ const element = document.createElement('div')
21
+ const root = createRoot(element)
22
+ const onNavigate = vi.fn()
23
+ try {
24
+ await act(async () =>
25
+ root.render(
26
+ <KedaTriggerAuthRenderer
27
+ data={{
28
+ kind: 'TriggerAuthentication',
29
+ metadata: { namespace: 'app' },
30
+ spec,
31
+ }}
32
+ onNavigate={onNavigate}
33
+ />,
34
+ ),
35
+ )
36
+ const button = [...element.querySelectorAll('button')].find(b => b.textContent === 'iam')
37
+ expect(button).toBeDefined()
38
+ await act(async () => button!.click())
39
+ expect(onNavigate).toHaveBeenCalledWith({
40
+ kind: 'secrets',
41
+ group: '',
42
+ namespace: 'app',
43
+ name: 'iam',
44
+ })
45
+ expect(element.textContent).toContain('credentials.json')
46
+ } finally {
47
+ await act(async () => root.unmount())
48
+ }
49
+ })
50
+
51
+ it('retains cluster-auth names without inventing an operator credential namespace', () => {
52
+ const html = renderToStaticMarkup(
53
+ <KedaTriggerAuthRenderer
54
+ data={{
55
+ kind: 'ClusterTriggerAuthentication',
56
+ metadata: { name: 'cluster' },
57
+ spec,
58
+ }}
59
+ onNavigate={() => {}}
60
+ />,
61
+ )
62
+ const doc = new DOMParser().parseFromString(html, 'text/html')
63
+ expect(doc.body.textContent).toContain('iam')
64
+ expect(doc.body.textContent).toContain('direct')
65
+ expect([...doc.querySelectorAll('button')].some(b => b.textContent === 'iam' || b.textContent === 'direct')).toBe(
66
+ false,
67
+ )
68
+ })
69
+
70
+ it('does not accept the previously invented shallow GCP reference shape', () => {
71
+ const html = renderToStaticMarkup(
72
+ <KedaTriggerAuthRenderer
73
+ data={{
74
+ kind: 'TriggerAuthentication',
75
+ metadata: { namespace: 'app' },
76
+ spec: {
77
+ gcpSecretManager: {
78
+ credentials: { clientSecret: { name: 'fictional' } },
79
+ },
80
+ },
81
+ }}
82
+ />,
83
+ )
84
+ expect(html).not.toContain('fictional')
85
+ })
@@ -1,13 +1,14 @@
1
1
  import { Cpu, Settings } from 'lucide-react'
2
2
  import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown} from '../../ui/drawer-components'
3
- import { kindToPlural } from '../../../utils/navigation'
3
+ import { objectReferenceToResourceRef } from '../../../utils/navigation'
4
+ import type { ResourceRef } from '../../../types'
5
+ import { TimeValue } from '../../ui/ScheduleValue'
4
6
  import {
5
7
  getScaledObjectStatus,
6
8
  getScaledObjectTarget,
7
9
  getScaledObjectReplicas,
8
10
  getScaledObjectTriggers,
9
11
  getScaledObjectHpaName,
10
- getScaledObjectLastActiveTime,
11
12
  getScaledObjectPollingInterval,
12
13
  getScaledObjectCooldownPeriod,
13
14
  } from '../resource-utils-keda'
@@ -24,7 +25,7 @@ function summarizeScalingPolicies(policies: any[] | undefined, periodSeconds?: n
24
25
 
25
26
  interface KedaScaledObjectRendererProps {
26
27
  data: any
27
- onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
28
+ onNavigate?: (ref: ResourceRef) => void
28
29
  }
29
30
 
30
31
  export function KedaScaledObjectRenderer({ data, onNavigate }: KedaScaledObjectRendererProps) {
@@ -75,18 +76,19 @@ export function KedaScaledObjectRenderer({ data, onNavigate }: KedaScaledObjectR
75
76
  <Property label="Target" value={(() => {
76
77
  const target = data.spec?.scaleTargetRef
77
78
  if (target?.name) {
78
- return (
79
- <ResourceLink
80
- name={target.name}
81
- kind={kindToPlural(target.kind || 'Deployment')}
82
- namespace={data.metadata?.namespace || ''}
83
- label={getScaledObjectTarget(data)}
84
- onNavigate={onNavigate}
85
- />
86
- )
79
+ // KEDA documents these defaults for omitted fields; an explicit
80
+ // target API group still belongs to the declared custom resource.
81
+ const ref = objectReferenceToResourceRef({
82
+ ...target,
83
+ apiVersion: target.apiVersion || 'apps/v1',
84
+ kind: target.kind || 'Deployment',
85
+ namespace: data.metadata?.namespace,
86
+ })
87
+ return ref ? <ResourceLink {...ref} label={getScaledObjectTarget(data)} onNavigate={onNavigate} /> : getScaledObjectTarget(data)
87
88
  }
88
89
  return getScaledObjectTarget(data)
89
90
  })()} />
91
+ <Property label="Target API" value={data.spec?.scaleTargetRef?.apiVersion || 'apps/v1'} />
90
92
  <Property label="Replicas" value={getScaledObjectReplicas(data)} />
91
93
  <Property label="Polling Interval" value={`${getScaledObjectPollingInterval(data)}s`} />
92
94
  <Property label="Cooldown Period" value={`${getScaledObjectCooldownPeriod(data)}s`} />
@@ -97,7 +99,7 @@ export function KedaScaledObjectRenderer({ data, onNavigate }: KedaScaledObjectR
97
99
  }
98
100
  return hpaName
99
101
  })()} />
100
- <Property label="Last Active" value={getScaledObjectLastActiveTime(data)} />
102
+ <Property label="Last Active" value={<TimeValue timestamp={data.status?.lastActiveTime} />} />
101
103
  </PropertyList>
102
104
  {fallback && (
103
105
  <div className="mt-2 pt-2 border-t border-theme-border">
@@ -21,6 +21,8 @@ export function KedaTriggerAuthRenderer({ data, onNavigate }: KedaTriggerAuthRen
21
21
  const awsSecretManager = spec.awsSecretManager
22
22
  const gcpSecretManager = spec.gcpSecretManager
23
23
  const podIdentity = spec.podIdentity
24
+ const credentialRef = gcpSecretManager?.credentials?.clientSecret?.valueFrom?.secretKeyRef
25
+ const namespace = data.metadata?.namespace
24
26
 
25
27
  return (
26
28
  <>
@@ -48,7 +50,7 @@ export function KedaTriggerAuthRenderer({ data, onNavigate }: KedaTriggerAuthRen
48
50
  <div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
49
51
  <span className="text-theme-text-primary font-medium">{ref.parameter}</span>
50
52
  <span className="text-theme-text-secondary">
51
- Secret: <ResourceLink name={ref.name} kind="secrets" namespace={data.metadata?.namespace || ''} onNavigate={onNavigate} /> / {ref.key}
53
+ Secret: {namespace ? <ResourceLink name={ref.name} kind="secrets" group="" namespace={namespace} onNavigate={onNavigate} /> : ref.name} / {ref.key}
52
54
  </span>
53
55
  </div>
54
56
  </div>
@@ -150,9 +152,10 @@ export function KedaTriggerAuthRenderer({ data, onNavigate }: KedaTriggerAuthRen
150
152
  {gcpSecretManager && (
151
153
  <Section title="GCP Secret Manager" icon={Cloud}>
152
154
  <PropertyList>
153
- {gcpSecretManager.credentials?.clientSecret?.name && (
154
- <Property label="Credentials Secret" value={gcpSecretManager.credentials.clientSecret.name} />
155
+ {credentialRef?.name && (
156
+ <Property label="Credentials Secret" value={namespace ? <ResourceLink name={credentialRef.name} kind="secrets" group="" namespace={namespace} onNavigate={onNavigate} /> : credentialRef.name} />
155
157
  )}
158
+ {credentialRef?.key && <Property label="Credentials Key" value={credentialRef.key} />}
156
159
  </PropertyList>
157
160
  {gcpSecretManager.secrets && gcpSecretManager.secrets.length > 0 && (
158
161
  <div className="mt-2 space-y-1">
@@ -1,12 +1,12 @@
1
+ import { KnativeReferenceLink } from './KnativeReferenceLink'
1
2
  import { Radio, Filter, FileType, Inbox, ArrowRightLeft } from 'lucide-react'
2
3
  import { clsx } from 'clsx'
3
4
  import { Section, PropertyList, Property, ConditionsSection, KnativeNotReadyBanner, ResourceLink } from '../../ui/drawer-components'
4
- import { kindToPlural } from '../../../utils/navigation'
5
5
  import { getKnativeConditionStatus } from '../resource-utils-knative'
6
6
 
7
7
  interface RendererProps {
8
8
  data: any
9
- onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
9
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
10
10
  }
11
11
 
12
12
  // ============================================================================
@@ -47,10 +47,9 @@ export function BrokerRenderer({ data, onNavigate }: RendererProps) {
47
47
  <PropertyList>
48
48
  {delivery.deadLetterSink?.ref && (
49
49
  <Property label="Dead Letter" value={
50
- <ResourceLink
51
- name={delivery.deadLetterSink.ref.name}
52
- kind={kindToPlural(delivery.deadLetterSink.ref.kind || 'Service')}
53
- namespace={delivery.deadLetterSink.ref.namespace || ns}
50
+ <KnativeReferenceLink
51
+ reference={delivery.deadLetterSink.ref}
52
+ namespace={ns}
54
53
  onNavigate={onNavigate}
55
54
  />
56
55
  } />
@@ -96,19 +95,17 @@ export function TriggerRenderer({ data, onNavigate }: RendererProps) {
96
95
  </span>
97
96
  } />
98
97
  <Property label="Broker" value={
99
- <ResourceLink
100
- name={brokerName}
101
- kind="brokers"
102
- namespace={ns}
103
- onNavigate={onNavigate}
104
- />
98
+ spec.brokerRef
99
+ ? (spec.brokerRef.name && spec.brokerRef.namespace
100
+ ? <ResourceLink name={spec.brokerRef.name} kind="brokers" group="eventing.knative.dev" namespace={spec.brokerRef.namespace} onNavigate={onNavigate} />
101
+ : <span>{spec.brokerRef.name || '-'}</span>)
102
+ : <ResourceLink name={brokerName} kind="brokers" group="eventing.knative.dev" namespace={ns} onNavigate={onNavigate} />
105
103
  } />
106
104
  <Property label="Subscriber" value={
107
105
  subscriberRef ? (
108
- <ResourceLink
109
- name={subscriberRef.name}
110
- kind={kindToPlural(subscriberRef.kind || 'Service')}
111
- namespace={subscriberRef.namespace || ns}
106
+ <KnativeReferenceLink
107
+ reference={subscriberRef}
108
+ namespace={ns}
112
109
  onNavigate={onNavigate}
113
110
  />
114
111
  ) : subscriberUri ? (
@@ -247,9 +244,8 @@ export function SubscriptionRenderer({ data, onNavigate }: RendererProps) {
247
244
  } />
248
245
  {channelRef && (
249
246
  <Property label="Channel" value={
250
- <ResourceLink
251
- name={channelRef.name}
252
- kind={kindToPlural(channelRef.kind || 'Channel')}
247
+ <KnativeReferenceLink
248
+ reference={channelRef}
253
249
  namespace={ns}
254
250
  onNavigate={onNavigate}
255
251
  />
@@ -257,10 +253,9 @@ export function SubscriptionRenderer({ data, onNavigate }: RendererProps) {
257
253
  )}
258
254
  <Property label="Subscriber" value={
259
255
  subscriberRef ? (
260
- <ResourceLink
261
- name={subscriberRef.name}
262
- kind={kindToPlural(subscriberRef.kind || 'Service')}
263
- namespace={subscriberRef.namespace || ns}
256
+ <KnativeReferenceLink
257
+ reference={subscriberRef}
258
+ namespace={ns}
264
259
  onNavigate={onNavigate}
265
260
  />
266
261
  ) : subscriberUri ? (
@@ -269,10 +264,9 @@ export function SubscriptionRenderer({ data, onNavigate }: RendererProps) {
269
264
  } />
270
265
  <Property label="Reply" value={
271
266
  replyRef ? (
272
- <ResourceLink
273
- name={replyRef.name}
274
- kind={kindToPlural(replyRef.kind || 'Channel')}
275
- namespace={replyRef.namespace || ns}
267
+ <KnativeReferenceLink
268
+ reference={replyRef}
269
+ namespace={ns}
276
270
  onNavigate={onNavigate}
277
271
  />
278
272
  ) : replyUri ? (
@@ -287,10 +281,9 @@ export function SubscriptionRenderer({ data, onNavigate }: RendererProps) {
287
281
  <PropertyList>
288
282
  {delivery.deadLetterSink.ref && (
289
283
  <Property label="Sink" value={
290
- <ResourceLink
291
- name={delivery.deadLetterSink.ref.name}
292
- kind={kindToPlural(delivery.deadLetterSink.ref.kind || 'Service')}
293
- namespace={delivery.deadLetterSink.ref.namespace || ns}
284
+ <KnativeReferenceLink
285
+ reference={delivery.deadLetterSink.ref}
286
+ namespace={ns}
294
287
  onNavigate={onNavigate}
295
288
  />
296
289
  } />
@@ -0,0 +1,47 @@
1
+ // @vitest-environment jsdom
2
+ import { act, type ReactNode } from 'react'
3
+ import { createRoot } from 'react-dom/client'
4
+ import { describe, expect, it, vi } from 'vitest'
5
+ import { KnativeReferenceLink } from './KnativeReferenceLink'
6
+ import { TriggerRenderer } from './KnativeEventingRenderer'
7
+
8
+ async function rendered(element: ReactNode, check: (container: HTMLDivElement) => Promise<void> | void) {
9
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
10
+ const container = document.createElement('div')
11
+ const root = createRoot(container)
12
+ try { await act(async () => root.render(element)); await check(container) }
13
+ finally { await act(async () => root.unmount()); Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false }) }
14
+ }
15
+ async function click(container: HTMLDivElement, text: string) {
16
+ const button = [...container.querySelectorAll('button')].find(button => button.textContent === text)
17
+ expect(button, `missing link ${text}`).toBeDefined()
18
+ await act(async () => button!.click())
19
+ }
20
+
21
+ describe('exact reference navigation', () => {
22
+ it('follows the Trigger broker reconciler namespace/name contract',async()=>{
23
+ const navigate=vi.fn()
24
+ await rendered(<TriggerRenderer data={{metadata:{namespace:'app'},spec:{brokerRef:{namespace:'events',name:'actual',kind:'Service',apiVersion:'v1'}}}} onNavigate={navigate}/>,async container=>{
25
+ await click(container,'actual');expect(navigate).toHaveBeenCalledWith({kind:'brokers',group:'eventing.knative.dev',namespace:'events',name:'actual'})
26
+ })
27
+ })
28
+
29
+ it.each([
30
+ {apiVersion:'messaging.knative.dev/v1',kind:'InMemoryChannel',name:'events'},
31
+ {group:'messaging.knative.dev',kind:'InMemoryChannel',name:'events'},
32
+ ])('uses the actual Knative target group and kind: %j', async reference => {
33
+ const navigate=vi.fn()
34
+ await rendered(<KnativeReferenceLink reference={reference} namespace="app" onNavigate={navigate}/>,async container=>{
35
+ await click(container,'events')
36
+ expect(navigate).toHaveBeenCalledWith({kind:'inmemorychannels',group:'messaging.knative.dev',namespace:'app',name:'events'})
37
+ })
38
+ })
39
+ it.each([
40
+ {kind:'Service',name:'events'},
41
+ {apiVersion:'v1',group:'serving.knative.dev',kind:'Service',name:'events'},
42
+ ])('leaves incomplete or conflicting Knative identity as text: %j', async reference=>{
43
+ await rendered(<KnativeReferenceLink reference={reference} namespace="app" onNavigate={vi.fn()}/>,container=>{
44
+ expect(container.textContent).toContain('events');expect(container.querySelector('button')).toBeNull()
45
+ })
46
+ })
47
+ })
@@ -1,12 +1,12 @@
1
+ import { KnativeReferenceLink } from './KnativeReferenceLink'
1
2
  import { ListOrdered, GitFork } from 'lucide-react'
2
3
  import { clsx } from 'clsx'
3
- import { Section, PropertyList, Property, ConditionsSection, KnativeNotReadyBanner, ResourceLink } from '../../ui/drawer-components'
4
- import { kindToPlural } from '../../../utils/navigation'
4
+ import { Section, PropertyList, Property, ConditionsSection, KnativeNotReadyBanner } from '../../ui/drawer-components'
5
5
  import { getKnativeConditionStatus } from '../resource-utils-knative'
6
6
 
7
7
  interface RendererProps {
8
8
  data: any
9
- onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
9
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
10
10
  }
11
11
 
12
12
  function RefDisplay({ ref: destRef, ns, onNavigate }: { ref: any; ns: string; onNavigate?: RendererProps['onNavigate'] }) {
@@ -14,10 +14,9 @@ function RefDisplay({ ref: destRef, ns, onNavigate }: { ref: any; ns: string; on
14
14
 
15
15
  if (destRef.ref) {
16
16
  return (
17
- <ResourceLink
18
- name={destRef.ref.name}
19
- kind={kindToPlural(destRef.ref.kind || 'Service')}
20
- namespace={destRef.ref.namespace || ns}
17
+ <KnativeReferenceLink
18
+ reference={destRef.ref}
19
+ namespace={ns}
21
20
  onNavigate={onNavigate}
22
21
  />
23
22
  )