@skyhook-io/k8s-ui 1.17.0 → 1.18.0

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 (250) hide show
  1. package/README.md +41 -0
  2. package/package.json +6 -6
  3. package/src/components/charts/AreaChart.interaction.test.tsx +118 -0
  4. package/src/components/charts/AreaChart.test.tsx +25 -0
  5. package/src/components/charts/AreaChart.tsx +141 -12
  6. package/src/components/charts/index.ts +1 -0
  7. package/src/components/checks/ChecksView.tsx +18 -1
  8. package/src/components/cnpg/CNPGBackupSummary.tsx +57 -9
  9. package/src/components/cnpg/CNPGClusterHASection.test.tsx +90 -0
  10. package/src/components/cnpg/CNPGClusterHASection.tsx +331 -0
  11. package/src/components/cnpg/CNPGClusterSummary.test.tsx +275 -0
  12. package/src/components/cnpg/CNPGClusterSummary.tsx +310 -96
  13. package/src/components/cnpg/CNPGConnectSection.test.tsx +64 -0
  14. package/src/components/cnpg/CNPGConnectSection.tsx +178 -0
  15. package/src/components/cnpg/CNPGDeclarativeSummary.tsx +157 -26
  16. package/src/components/cnpg/CNPGImageCatalogSummary.tsx +7 -4
  17. package/src/components/cnpg/CNPGLogicalPath.test.tsx +22 -0
  18. package/src/components/cnpg/CNPGLogicalPath.tsx +150 -0
  19. package/src/components/cnpg/CNPGObjectStoreSummary.tsx +9 -6
  20. package/src/components/cnpg/CNPGObjectSummary.test.tsx +193 -2
  21. package/src/components/cnpg/CNPGPoolerScheduling.test.tsx +36 -0
  22. package/src/components/cnpg/CNPGPoolerSummary.tsx +241 -14
  23. package/src/components/cnpg/CNPGSharedSummary.tsx +15 -8
  24. package/src/components/cnpg/CNPGWALArchivingFact.test.tsx +39 -0
  25. package/src/components/cnpg/backupRuns.test.ts +17 -0
  26. package/src/components/cnpg/backupRuns.ts +19 -0
  27. package/src/components/cnpg/connect.test.ts +124 -0
  28. package/src/components/cnpg/connect.ts +155 -0
  29. package/src/components/cnpg/databaseRole.test.ts +38 -0
  30. package/src/components/cnpg/databaseRole.ts +80 -0
  31. package/src/components/cnpg/ha.test.ts +446 -0
  32. package/src/components/cnpg/ha.ts +621 -0
  33. package/src/components/cnpg/index.ts +18 -0
  34. package/src/components/cnpg/logicalReplication.test.ts +121 -0
  35. package/src/components/cnpg/logicalReplication.ts +252 -0
  36. package/src/components/cnpg/pooler.test.ts +84 -0
  37. package/src/components/cnpg/pooler.ts +225 -0
  38. package/src/components/cnpg/primitives.tsx +21 -117
  39. package/src/components/cnpg/problems.ts +12 -0
  40. package/src/components/cnpg/relations.test.ts +63 -13
  41. package/src/components/cnpg/relations.ts +61 -52
  42. package/src/components/cnpg/schedule.ts +35 -0
  43. package/src/components/cnpg/workspace-disk.test.ts +97 -0
  44. package/src/components/cnpg/workspace-fleet-metrics.test.ts +250 -0
  45. package/src/components/cnpg/workspace-problems.test.ts +175 -0
  46. package/src/components/cnpg/workspace.test.ts +380 -9
  47. package/src/components/cnpg/workspace.ts +1137 -136
  48. package/src/components/dock/DockContext.test.tsx +32 -0
  49. package/src/components/dock/DockContext.tsx +18 -4
  50. package/src/components/dock/LocalTerminalTab.test.tsx +40 -0
  51. package/src/components/dock/LocalTerminalTab.tsx +4 -3
  52. package/src/components/dock/TerminalTab.tsx +20 -8
  53. package/src/components/facts/certainty.tsx +36 -0
  54. package/src/components/facts/facts.test.tsx +24 -0
  55. package/src/components/facts/facts.tsx +62 -0
  56. package/src/components/facts/index.ts +6 -0
  57. package/src/components/facts/managed-by.test.tsx +17 -0
  58. package/src/components/facts/managed-by.tsx +36 -0
  59. package/src/components/facts/public-exports.test.tsx +35 -0
  60. package/src/components/gitops/GitOpsGraphFilterRail.tsx +7 -3
  61. package/src/components/gitops/GitOpsHealthSourceNotice.test.tsx +38 -0
  62. package/src/components/gitops/GitOpsHealthSourceNotice.tsx +13 -3
  63. package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +142 -0
  64. package/src/components/gitops/insights/ArgoResourceDiff.tsx +22 -5
  65. package/src/components/gitops/insights/DriftPanel.tsx +62 -0
  66. package/src/components/gitops/insights/GitOpsInsightViews.tsx +40 -81
  67. package/src/components/gitops/insights/insights-helpers.test.ts +18 -0
  68. package/src/components/gitops/insights/insights-helpers.ts +7 -0
  69. package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +91 -2
  70. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +76 -76
  71. package/src/components/gitops/tree/layout.test.ts +111 -0
  72. package/src/components/gitops/tree/layout.ts +128 -0
  73. package/src/components/gitops/tree/tree-helpers.test.ts +23 -0
  74. package/src/components/gitops/tree/tree-helpers.ts +12 -2
  75. package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
  76. package/src/components/issues/IssuesView.tsx +44 -6
  77. package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
  78. package/src/components/issues/ResourceIssuesSection.tsx +8 -2
  79. package/src/components/issues/diagnostic.ts +2 -0
  80. package/src/components/issues/index.ts +3 -1
  81. package/src/components/issues/issue-timing.ts +12 -1
  82. package/src/components/issues/issues.test.ts +75 -1
  83. package/src/components/issues/severity.ts +30 -2
  84. package/src/components/issues/types.ts +55 -4
  85. package/src/components/logs/LogCore.theme.test.tsx +206 -3
  86. package/src/components/logs/LogCore.tsx +180 -53
  87. package/src/components/logs/LogToolbarSelects.tsx +3 -1
  88. package/src/components/logs/LogsViewer.test.tsx +369 -0
  89. package/src/components/logs/LogsViewer.tsx +91 -13
  90. package/src/components/logs/StructuredLogLine.test.tsx +76 -0
  91. package/src/components/logs/StructuredLogLine.tsx +23 -31
  92. package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
  93. package/src/components/logs/WorkloadLogsViewer.tsx +46 -11
  94. package/src/components/logs/useLogBuffer.reading.test.tsx +149 -0
  95. package/src/components/logs/useLogBuffer.test.ts +8 -0
  96. package/src/components/logs/useLogBuffer.ts +48 -18
  97. package/src/components/logs/useLogSearch.ts +3 -1
  98. package/src/components/logs/useLogStream.ts +17 -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/ColumnPickerList.test.tsx +46 -0
  105. package/src/components/resources/ColumnPickerList.tsx +174 -0
  106. package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
  107. package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
  108. package/src/components/resources/ResourcesSidebar.tsx +92 -41
  109. package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
  110. package/src/components/resources/ResourcesView.kind-view.test.tsx +296 -0
  111. package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
  112. package/src/components/resources/ResourcesView.tsx +392 -181
  113. package/src/components/resources/column-order.test.ts +104 -0
  114. package/src/components/resources/column-order.ts +64 -0
  115. package/src/components/resources/curated-column-ownership.test.ts +3 -3
  116. package/src/components/resources/renderers/ArgoApplicationRenderer.test.tsx +15 -0
  117. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +4 -1
  118. package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
  119. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +3 -2
  120. package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
  121. package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
  122. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
  123. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +5 -7
  124. package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
  125. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
  126. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +7 -15
  127. package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
  128. package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
  129. package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
  130. package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
  131. package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
  132. package/src/components/resources/renderers/CronJobRenderer.test.tsx +31 -0
  133. package/src/components/resources/renderers/CronJobRenderer.tsx +28 -20
  134. package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +39 -1
  135. package/src/components/resources/renderers/EndpointSliceRenderer.tsx +14 -13
  136. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +22 -9
  137. package/src/components/resources/renderers/HPARenderer.tsx +56 -16
  138. package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
  139. package/src/components/resources/renderers/IngressRenderer.tsx +30 -3
  140. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
  141. package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
  142. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
  143. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +13 -11
  144. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
  145. package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
  146. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
  147. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
  148. package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
  149. package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
  150. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
  151. package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
  152. package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
  153. package/src/components/resources/renderers/PodRenderer.sidecar-terminal.test.tsx +200 -0
  154. package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
  155. package/src/components/resources/renderers/PodRenderer.tsx +81 -40
  156. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -1
  157. package/src/components/resources/renderers/VPARenderer.tsx +17 -11
  158. package/src/components/resources/resource-utils-capi.ts +1 -1
  159. package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
  160. package/src/components/resources/resource-utils-cnpg.ts +51 -20
  161. package/src/components/resources/resource-utils-contour.test.ts +44 -0
  162. package/src/components/resources/resource-utils-contour.ts +16 -0
  163. package/src/components/resources/resource-utils-istio.ts +12 -8
  164. package/src/components/resources/resource-utils-knative.ts +18 -0
  165. package/src/components/resources/resource-utils.ts +96 -7
  166. package/src/components/resources/rules-column-width.test.ts +1 -1
  167. package/src/components/resources/summarize-scheduler-message.test.ts +31 -0
  168. package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
  169. package/src/components/shared/ActionConfirmDialog.tsx +246 -0
  170. package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
  171. package/src/components/shared/CreateResourceDialog.tsx +126 -80
  172. package/src/components/shared/DetailShell.test.tsx +80 -0
  173. package/src/components/shared/DetailShell.tsx +85 -10
  174. package/src/components/shared/DrainPlanDialog.test.tsx +7 -0
  175. package/src/components/shared/DrainPlanDialog.tsx +6 -2
  176. package/src/components/shared/EditableYamlView.test.tsx +7 -0
  177. package/src/components/shared/EditableYamlView.tsx +1 -1
  178. package/src/components/shared/ResourceActionsBar.tsx +14 -7
  179. package/src/components/shared/ResourceRendererDispatch.tsx +15 -10
  180. package/src/components/shared/index.ts +2 -0
  181. package/src/components/timeline/DiffViewer.test.tsx +69 -0
  182. package/src/components/timeline/DiffViewer.tsx +113 -4
  183. package/src/components/timeline/TimelineSwimlanes.tsx +1 -1
  184. package/src/components/timeline/shared.test.tsx +48 -0
  185. package/src/components/timeline/shared.tsx +14 -19
  186. package/src/components/timeline/timeline-filters.ts +1 -0
  187. package/src/components/topology/K8sResourceNode.test.ts +18 -1
  188. package/src/components/topology/K8sResourceNode.tsx +44 -2
  189. package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
  190. package/src/components/topology/TopologyGraph.tsx +3 -71
  191. package/src/components/topology/TopologySearch.tsx +1 -0
  192. package/src/components/topology/internet-groups.test.ts +35 -0
  193. package/src/components/topology/internet-groups.ts +55 -0
  194. package/src/components/topology/layout.ts +1 -1
  195. package/src/components/ui/Badge.tsx +1 -0
  196. package/src/components/ui/FoldSection.tsx +68 -0
  197. package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
  198. package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
  199. package/src/components/ui/RefLink.tsx +20 -0
  200. package/src/components/ui/RowActionMenu.tsx +8 -3
  201. package/src/components/ui/Toast.tsx +15 -4
  202. package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
  203. package/src/components/ui/drawer-components.test.tsx +110 -1
  204. package/src/components/ui/drawer-components.tsx +62 -9
  205. package/src/components/ui/index.ts +4 -2
  206. package/src/components/ui/monacoEditorFeatures.ts +77 -0
  207. package/src/components/ui/monacoRuntime.ts +14 -19
  208. package/src/components/ui/status-tone.test.ts +10 -1
  209. package/src/components/ui/status-tone.tsx +29 -0
  210. package/src/components/workload/WorkloadView.deleted.test.tsx +43 -0
  211. package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
  212. package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
  213. package/src/components/workload/WorkloadView.tsx +193 -71
  214. package/src/index.ts +14 -1
  215. package/src/monaco-deep.d.ts +19 -12
  216. package/src/theme/components.css +13 -0
  217. package/src/types/capacity.ts +11 -2
  218. package/src/types/core.ts +53 -2
  219. package/src/types/gitops-flux-status.test.ts +32 -1
  220. package/src/types/gitops-insights.ts +20 -2
  221. package/src/types/gitops-tree.ts +12 -0
  222. package/src/types/gitops.ts +26 -6
  223. package/src/utils/api-resources.ts +7 -0
  224. package/src/utils/application-history.ts +1 -0
  225. package/src/utils/cnpg-backup.test.ts +26 -0
  226. package/src/utils/cnpg-backup.ts +70 -0
  227. package/src/utils/format.test.ts +9 -0
  228. package/src/utils/format.ts +4 -0
  229. package/src/utils/gitops-owner.ts +3 -2
  230. package/src/utils/grant.test.ts +19 -0
  231. package/src/utils/grant.ts +35 -0
  232. package/src/utils/index.ts +4 -0
  233. package/src/utils/k8s-errors.ts +7 -7
  234. package/src/utils/log-format.test.ts +41 -0
  235. package/src/utils/log-format.ts +71 -15
  236. package/src/utils/log-level.ts +12 -8
  237. package/src/utils/navigation.test.ts +57 -0
  238. package/src/utils/navigation.ts +86 -8
  239. package/src/utils/object-reference-navigation.test.ts +43 -0
  240. package/src/utils/resource-hierarchy.test.ts +37 -0
  241. package/src/utils/resource-hierarchy.ts +15 -7
  242. package/src/utils/resource-icons.ts +2 -0
  243. package/src/utils/skeleton-yaml.test.ts +21 -0
  244. package/src/utils/skeleton-yaml.ts +16 -0
  245. package/src/utils/url-path.test.ts +35 -0
  246. package/src/utils/url-path.ts +21 -0
  247. package/src/utils/validators.test.ts +4 -13
  248. package/src/utils/validators.ts +5 -19
  249. package/src/utils/view-transition.test.ts +35 -1
  250. package/src/utils/view-transition.ts +16 -1
@@ -1,16 +1,17 @@
1
1
  import type { ReactNode } from 'react'
2
2
  import { Activity, Cpu } from 'lucide-react'
3
3
  import { clsx } from 'clsx'
4
- import { Section, PropertyList, Property, ConditionsSection, ResourceLink, type ConditionTone } from '../../ui/drawer-components'
4
+ import { Section, PropertyList, Property, ConditionsSection, ResourceLink, LabelSelectorDisplay, type ConditionTone } from '../../ui/drawer-components'
5
5
  import { Badge } from '../../ui/Badge'
6
- import { kindToPlural } from '../../../utils/navigation'
6
+ import { objectReferenceToResourceRef, apiVersionToGroup } from '../../../utils/navigation'
7
+ import type { ResourceRef } from '../../../types'
8
+ import { TimeValue } from '../../ui/ScheduleValue'
7
9
  import { HPADiagnosisSummary } from '../HPADiagnosisSummary'
8
10
  import type { HPADiagnosis } from '../../../types'
9
- import { TimeValue } from '../../ui/ScheduleValue'
10
11
 
11
12
  interface HPARendererProps {
12
13
  data: any
13
- onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
14
+ onNavigate?: (ref: ResourceRef) => void
14
15
  hpaDiagnosis?: HPADiagnosis
15
16
  /** Optional host-provided section rendered after Conditions — used to inject Prometheus-backed charts. */
16
17
  extraSections?: ReactNode
@@ -37,6 +38,11 @@ export function HPARenderer({ data, onNavigate, hpaDiagnosis, extraSections }: H
37
38
  const status = data.status || {}
38
39
  const spec = data.spec || {}
39
40
  const metrics = status.currentMetrics || []
41
+ const namespace = data.metadata?.namespace
42
+ const targetRef = spec.scaleTargetRef || {}
43
+ const target = objectReferenceToResourceRef({ ...targetRef, namespace })
44
+ const hasDiagnosisMetrics = !!hpaDiagnosis?.metrics?.length
45
+ const objectMetrics = (spec.metrics || []).filter((metric: any) => metric.type === 'Object' && metric.object)
40
46
 
41
47
  return (
42
48
  <>
@@ -49,15 +55,9 @@ export function HPARenderer({ data, onNavigate, hpaDiagnosis, extraSections }: H
49
55
  <Section title="Scaling" icon={Cpu}>
50
56
  <PropertyList>
51
57
  <Property label="Target" value={
52
- spec.scaleTargetRef?.name ? (
53
- <ResourceLink
54
- name={spec.scaleTargetRef.name}
55
- kind={kindToPlural(spec.scaleTargetRef.kind || 'Deployment')}
56
- namespace={data.metadata?.namespace || ''}
57
- label={`${spec.scaleTargetRef.kind}/${spec.scaleTargetRef.name}`}
58
- onNavigate={onNavigate}
59
- />
60
- ) : undefined
58
+ targetRef.name ? (target ? (
59
+ <ResourceLink {...target} label={`${targetRef.kind}/${targetRef.name}`} onNavigate={onNavigate} />
60
+ ) : `${targetRef.kind || 'Unknown kind'}/${targetRef.name}`) : undefined
61
61
  } />
62
62
  <Property label="Current" value={status.currentReplicas} />
63
63
  <Property label="Desired" value={status.desiredReplicas} />
@@ -70,11 +70,11 @@ export function HPARenderer({ data, onNavigate, hpaDiagnosis, extraSections }: H
70
70
  {hpaDiagnosis?.metrics && hpaDiagnosis.metrics.length > 0 && (
71
71
  <Section title="Metrics" defaultExpanded>
72
72
  <div className="space-y-2">
73
- {hpaDiagnosis.metrics.map((metric) => (
74
- <div key={`${metric.type}-${metric.name}`} className="card-inner">
73
+ {hpaDiagnosis.metrics.map((metric, index) => (
74
+ <div key={`${metric.type}-${metric.name}-${index}`} className="card-inner">
75
75
  <div className="flex flex-wrap items-center justify-between gap-2 text-sm">
76
76
  <div>
77
- <div className="font-medium text-theme-text-primary">{metric.name}</div>
77
+ <div className="font-medium text-theme-text-primary break-words">{spec.metrics?.[index]?.type === 'Object' ? spec.metrics[index].object.metric.name : metric.name}</div>
78
78
  <div className="text-xs text-theme-text-tertiary">{metric.type}</div>
79
79
  </div>
80
80
  <div className="flex flex-wrap items-center gap-2 text-xs text-theme-text-secondary">
@@ -85,6 +85,11 @@ export function HPARenderer({ data, onNavigate, hpaDiagnosis, extraSections }: H
85
85
  </Badge>
86
86
  </div>
87
87
  </div>
88
+ {spec.metrics?.[index]?.type === 'Object' && (
89
+ <div className="mt-2">
90
+ <HPAObjectMetricSource object={spec.metrics[index].object} namespace={namespace} onNavigate={onNavigate} />
91
+ </div>
92
+ )}
88
93
  </div>
89
94
  ))}
90
95
  </div>
@@ -121,9 +126,44 @@ export function HPARenderer({ data, onNavigate, hpaDiagnosis, extraSections }: H
121
126
  </Section>
122
127
  )}
123
128
 
129
+ {!hasDiagnosisMetrics && objectMetrics.length > 0 && (
130
+ <Section title="Object metric sources" defaultExpanded>
131
+ <div className="space-y-3">
132
+ {objectMetrics.map((metric: any, i: number) => (
133
+ <div key={`${metric.object.metric.name}-${i}`} className="card-inner">
134
+ <HPAObjectMetricSource object={metric.object} namespace={namespace} onNavigate={onNavigate} showMetric />
135
+ </div>
136
+ ))}
137
+ </div>
138
+ </Section>
139
+ )}
140
+
124
141
  <ConditionsSection conditions={status.conditions} getConditionTone={hpaConditionTone} />
125
142
 
126
143
  {extraSections}
127
144
  </>
128
145
  )
129
146
  }
147
+
148
+ function HPAObjectMetricSource({ object, namespace, onNavigate, showMetric = false }: {
149
+ object: any
150
+ namespace?: string
151
+ onNavigate?: (ref: ResourceRef) => void
152
+ showMetric?: boolean
153
+ }) {
154
+ const described = object.describedObject || {}
155
+ const namespaceMetric = described.kind === 'Namespace' && apiVersionToGroup(described.apiVersion) === ''
156
+ const name = namespaceMetric ? namespace : described.name
157
+ const source = objectReferenceToResourceRef({ ...described, name, namespace })
158
+ return (
159
+ <PropertyList>
160
+ {showMetric && <Property label="Metric" value={object.metric.name} />}
161
+ <Property label="Object" value={source ? (
162
+ <ResourceLink {...source} label={`${described.kind}/${name}`} onNavigate={onNavigate} />
163
+ ) : described.name ? `${described.kind || 'Unknown kind'}/${described.name}` : undefined} />
164
+ {source?.group && <Property label="API Group" value={source.group} />}
165
+ {object.metric.selector && <Property label="Selector" value={<LabelSelectorDisplay selector={object.metric.selector} />} />}
166
+ {namespaceMetric && <Property label="Scope" value="This HPA's namespace" />}
167
+ </PropertyList>
168
+ )
169
+ }
@@ -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,6 +1,7 @@
1
1
  import { Globe, Shield, Clock } from 'lucide-react'
2
2
  import type { ResourceRef } from '../../../types'
3
3
  import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
4
+ import { kindToPluralWithGroup } from '../../../utils/navigation'
4
5
 
5
6
  interface IngressRendererProps {
6
7
  data: any
@@ -16,7 +17,10 @@ export function IngressRenderer({ data, onNavigate }: IngressRendererProps) {
16
17
  // Check for issues
17
18
  const hasNoAddress = lbIngress.length === 0
18
19
  const hasNoClass = !spec.ingressClassName && !data.metadata?.annotations?.['kubernetes.io/ingress.class']
19
- 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
20
24
 
21
25
  return (
22
26
  <>
@@ -50,11 +54,34 @@ export function IngressRenderer({ data, onNavigate }: IngressRendererProps) {
50
54
  {lbIngress.length > 0 && (
51
55
  <Property label="Address" value={lbIngress[0].ip || lbIngress[0].hostname} />
52
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
+ )}
53
80
  <Property label="TLS" value={tls.length > 0 ? `${tls.length} certificate(s)` : 'None'} />
54
81
  </PropertyList>
55
82
  </Section>
56
83
 
57
- <Section title="Rules" defaultExpanded>
84
+ {rules.length > 0 && <Section title="Rules" defaultExpanded>
58
85
  <div className="space-y-3">
59
86
  {rules.map((rule: any, i: number) => (
60
87
  <div key={i} className="card-inner-lg">
@@ -87,7 +114,7 @@ export function IngressRenderer({ data, onNavigate }: IngressRendererProps) {
87
114
  </div>
88
115
  ))}
89
116
  </div>
90
- </Section>
117
+ </Section>}
91
118
 
92
119
  {tls.length > 0 && (
93
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
  )}
@@ -1,6 +1,7 @@
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'
4
5
  import { TimeValue } from '../../ui/ScheduleValue'
5
6
  import {
6
7
  getScaledObjectStatus,
@@ -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`} />
@@ -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
  )
@@ -1,13 +1,13 @@
1
+ import { KnativeReferenceLink } from './KnativeReferenceLink'
1
2
  import { Network, ShieldCheck, Server, Globe } from 'lucide-react'
2
3
  import { clsx } from 'clsx'
3
- import { Section, PropertyList, Property, ConditionsSection, KnativeNotReadyBanner, ResourceLink } from '../../ui/drawer-components'
4
+ import { Section, PropertyList, Property, ConditionsSection, KnativeNotReadyBanner } from '../../ui/drawer-components'
4
5
  import { Badge } from '../../ui/Badge'
5
- import { kindToPlural } from '../../../utils/navigation'
6
6
  import { getKnativeConditionStatus } from '../resource-utils-knative'
7
7
 
8
8
  interface RendererProps {
9
9
  data: any
10
- onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
10
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
11
11
  }
12
12
 
13
13
  // ============================================================================
@@ -166,9 +166,8 @@ export function ServerlessServiceRenderer({ data, onNavigate }: RendererProps) {
166
166
  <Property label="Protocol" value={protocolType} />
167
167
  {objectRef && (
168
168
  <Property label="Target" value={
169
- <ResourceLink
170
- name={objectRef.name}
171
- kind={kindToPlural(objectRef.kind || 'Deployment')}
169
+ <KnativeReferenceLink
170
+ reference={objectRef}
172
171
  namespace={ns}
173
172
  onNavigate={onNavigate}
174
173
  />
@@ -213,9 +212,8 @@ export function DomainMappingRenderer({ data, onNavigate }: RendererProps) {
213
212
  ) : undefined} />
214
213
  {ref && (
215
214
  <Property label="Target" value={
216
- <ResourceLink
217
- name={ref.name}
218
- kind={kindToPlural(ref.kind || 'Service')}
215
+ <KnativeReferenceLink
216
+ reference={ref}
219
217
  namespace={ns}
220
218
  onNavigate={onNavigate}
221
219
  />
@@ -0,0 +1,14 @@
1
+ import { ResourceLink } from '../../ui/drawer-components'
2
+ import type { ResourceRef } from '../../../types/core'
3
+ import { getKnativeReference } from '../resource-utils-knative'
4
+
5
+ export function KnativeReferenceLink({ reference, namespace, onNavigate }: {
6
+ reference: any
7
+ namespace: string
8
+ onNavigate?: (ref: ResourceRef) => void
9
+ }) {
10
+ const resolved = getKnativeReference(reference, namespace)
11
+ return resolved
12
+ ? <ResourceLink {...resolved} onNavigate={onNavigate} />
13
+ : <span className="text-theme-text-primary">{reference?.name || '-'}</span>
14
+ }