@skyhook-io/k8s-ui 1.17.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/README.md +41 -0
  2. package/package.json +3 -3
  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 +6 -2
  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 +115 -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 +24 -71
  67. package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
  68. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +38 -55
  69. package/src/components/gitops/tree/layout.test.ts +111 -0
  70. package/src/components/gitops/tree/layout.ts +128 -0
  71. package/src/components/gitops/tree/tree-helpers.test.ts +17 -0
  72. package/src/components/gitops/tree/tree-helpers.ts +7 -1
  73. package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
  74. package/src/components/issues/IssuesView.tsx +40 -5
  75. package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
  76. package/src/components/issues/ResourceIssuesSection.tsx +3 -0
  77. package/src/components/issues/index.ts +3 -1
  78. package/src/components/issues/issue-timing.ts +12 -1
  79. package/src/components/issues/issues.test.ts +49 -1
  80. package/src/components/issues/severity.ts +21 -0
  81. package/src/components/issues/types.ts +23 -0
  82. package/src/components/logs/LogCore.theme.test.tsx +81 -3
  83. package/src/components/logs/LogCore.tsx +25 -17
  84. package/src/components/logs/LogToolbarSelects.tsx +3 -1
  85. package/src/components/logs/LogsViewer.test.tsx +291 -0
  86. package/src/components/logs/LogsViewer.tsx +72 -11
  87. package/src/components/logs/StructuredLogLine.test.tsx +76 -0
  88. package/src/components/logs/StructuredLogLine.tsx +22 -30
  89. package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
  90. package/src/components/logs/WorkloadLogsViewer.tsx +39 -10
  91. package/src/components/logs/useLogBuffer.test.ts +8 -0
  92. package/src/components/logs/useLogStream.ts +17 -1
  93. package/src/components/problems/index.ts +1 -0
  94. package/src/components/problems/problems.test.tsx +63 -0
  95. package/src/components/problems/problems.tsx +165 -0
  96. package/src/components/resources/ActiveFilterBar.test.tsx +78 -0
  97. package/src/components/resources/ActiveFilterBar.tsx +86 -0
  98. package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
  99. package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
  100. package/src/components/resources/ResourcesSidebar.tsx +92 -41
  101. package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
  102. package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -0
  103. package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
  104. package/src/components/resources/ResourcesView.tsx +302 -109
  105. package/src/components/resources/curated-column-ownership.test.ts +3 -3
  106. package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
  107. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +3 -2
  108. package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
  109. package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
  110. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
  111. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +5 -7
  112. package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
  113. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
  114. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +7 -15
  115. package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
  116. package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
  117. package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
  118. package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
  119. package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
  120. package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +39 -1
  121. package/src/components/resources/renderers/EndpointSliceRenderer.tsx +14 -13
  122. package/src/components/resources/renderers/HPARenderer.tsx +56 -16
  123. package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
  124. package/src/components/resources/renderers/IngressRenderer.tsx +30 -3
  125. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
  126. package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
  127. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
  128. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +13 -11
  129. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
  130. package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
  131. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
  132. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
  133. package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
  134. package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
  135. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
  136. package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
  137. package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
  138. package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
  139. package/src/components/resources/renderers/PodRenderer.tsx +41 -29
  140. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -1
  141. package/src/components/resources/renderers/VPARenderer.tsx +17 -11
  142. package/src/components/resources/resource-utils-capi.ts +1 -1
  143. package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
  144. package/src/components/resources/resource-utils-cnpg.ts +51 -20
  145. package/src/components/resources/resource-utils-contour.test.ts +44 -0
  146. package/src/components/resources/resource-utils-contour.ts +16 -0
  147. package/src/components/resources/resource-utils-istio.ts +12 -8
  148. package/src/components/resources/resource-utils-knative.ts +18 -0
  149. package/src/components/resources/resource-utils.ts +82 -6
  150. package/src/components/resources/rules-column-width.test.ts +1 -1
  151. package/src/components/resources/summarize-scheduler-message.test.ts +30 -0
  152. package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
  153. package/src/components/shared/ActionConfirmDialog.tsx +246 -0
  154. package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
  155. package/src/components/shared/CreateResourceDialog.tsx +126 -80
  156. package/src/components/shared/DetailShell.test.tsx +80 -0
  157. package/src/components/shared/DetailShell.tsx +85 -10
  158. package/src/components/shared/EditableYamlView.test.tsx +7 -0
  159. package/src/components/shared/EditableYamlView.tsx +1 -1
  160. package/src/components/shared/ResourceActionsBar.tsx +11 -4
  161. package/src/components/shared/ResourceRendererDispatch.tsx +12 -9
  162. package/src/components/shared/index.ts +2 -0
  163. package/src/components/timeline/TimelineSwimlanes.tsx +1 -1
  164. package/src/components/timeline/timeline-filters.ts +1 -0
  165. package/src/components/topology/K8sResourceNode.test.ts +18 -1
  166. package/src/components/topology/K8sResourceNode.tsx +44 -2
  167. package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
  168. package/src/components/topology/TopologyGraph.tsx +3 -71
  169. package/src/components/topology/TopologySearch.tsx +1 -0
  170. package/src/components/topology/internet-groups.test.ts +35 -0
  171. package/src/components/topology/internet-groups.ts +55 -0
  172. package/src/components/topology/layout.ts +1 -1
  173. package/src/components/ui/Badge.tsx +1 -0
  174. package/src/components/ui/FoldSection.tsx +68 -0
  175. package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
  176. package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
  177. package/src/components/ui/RefLink.tsx +20 -0
  178. package/src/components/ui/RowActionMenu.tsx +8 -3
  179. package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
  180. package/src/components/ui/drawer-components.test.tsx +110 -1
  181. package/src/components/ui/drawer-components.tsx +62 -9
  182. package/src/components/ui/index.ts +3 -1
  183. package/src/components/ui/monacoEditorFeatures.ts +77 -0
  184. package/src/components/ui/monacoRuntime.ts +14 -19
  185. package/src/components/ui/status-tone.test.ts +10 -1
  186. package/src/components/ui/status-tone.tsx +29 -0
  187. package/src/components/workload/WorkloadView.deleted.test.tsx +43 -0
  188. package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
  189. package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
  190. package/src/components/workload/WorkloadView.tsx +180 -64
  191. package/src/index.ts +14 -1
  192. package/src/monaco-deep.d.ts +19 -12
  193. package/src/theme/components.css +13 -0
  194. package/src/types/capacity.ts +11 -2
  195. package/src/types/core.ts +29 -2
  196. package/src/types/gitops-flux-status.test.ts +32 -1
  197. package/src/types/gitops-insights.ts +19 -2
  198. package/src/types/gitops-tree.ts +10 -0
  199. package/src/types/gitops.ts +26 -6
  200. package/src/utils/api-resources.ts +7 -0
  201. package/src/utils/application-history.ts +1 -0
  202. package/src/utils/cnpg-backup.test.ts +26 -0
  203. package/src/utils/cnpg-backup.ts +70 -0
  204. package/src/utils/format.test.ts +9 -0
  205. package/src/utils/format.ts +4 -0
  206. package/src/utils/gitops-owner.ts +3 -2
  207. package/src/utils/grant.test.ts +19 -0
  208. package/src/utils/grant.ts +35 -0
  209. package/src/utils/index.ts +4 -0
  210. package/src/utils/k8s-errors.ts +7 -7
  211. package/src/utils/log-format.test.ts +41 -0
  212. package/src/utils/log-format.ts +71 -15
  213. package/src/utils/log-level.ts +12 -8
  214. package/src/utils/navigation.test.ts +57 -0
  215. package/src/utils/navigation.ts +86 -8
  216. package/src/utils/object-reference-navigation.test.ts +43 -0
  217. package/src/utils/resource-hierarchy.test.ts +37 -0
  218. package/src/utils/resource-hierarchy.ts +15 -7
  219. package/src/utils/resource-icons.ts +2 -0
  220. package/src/utils/skeleton-yaml.test.ts +21 -0
  221. package/src/utils/skeleton-yaml.ts +16 -0
  222. package/src/utils/url-path.test.ts +35 -0
  223. package/src/utils/url-path.ts +21 -0
  224. package/src/utils/validators.test.ts +4 -13
  225. package/src/utils/validators.ts +5 -19
  226. package/src/utils/view-transition.test.ts +35 -1
  227. package/src/utils/view-transition.ts +16 -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
  )
@@ -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
+ }
@@ -0,0 +1,60 @@
1
+ // @vitest-environment jsdom
2
+ import { act } from 'react'
3
+ import { createRoot } from 'react-dom/client'
4
+ import { describe, expect, it, vi } from 'vitest'
5
+ import { PingSourceRenderer } from './KnativeSourceRenderer'
6
+ import { DomainMappingRenderer } from './KnativeNetworkingRenderer'
7
+ import { initNavigationMap, resetNavigationMap } from '../../../utils/navigation'
8
+
9
+ describe('Knative reference links', () => {
10
+ it('keep the sink reference API group instead of resolving to the core Kind', async () => {
11
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
12
+ initNavigationMap([
13
+ { group: '', version: 'v1', kind: 'Service', name: 'services', namespaced: true, isCrd: false, verbs: ['get'] },
14
+ { group: 'serving.knative.dev', version: 'v1', kind: 'Service', name: 'services', namespaced: true, isCrd: true, verbs: ['get'] },
15
+ { group: 'relationships.radar.test', version: 'v1', kind: 'Service', name: 'virtualservices', namespaced: true, isCrd: true, verbs: ['get'] },
16
+ ])
17
+ const container = document.createElement('div')
18
+ const root = createRoot(container)
19
+ const onNavigate = vi.fn()
20
+ const clickSink = async (sinkRef: Record<string, string>) => {
21
+ await act(async () => root.render(<PingSourceRenderer onNavigate={onNavigate} data={{
22
+ metadata: { namespace: 'team', name: 'ping' },
23
+ spec: { schedule: '* * * * *', sink: { ref: sinkRef } },
24
+ }} />))
25
+ const link = [...container.querySelectorAll('button')].find(button => button.textContent === sinkRef.name)
26
+ expect(link).toBeDefined()
27
+ await act(async () => link!.click())
28
+ }
29
+ try {
30
+ await clickSink({ apiVersion: 'serving.knative.dev/v1', kind: 'Service', name: 'knative-backend' })
31
+ expect(onNavigate).toHaveBeenLastCalledWith(expect.objectContaining({ kind: 'services', group: 'serving.knative.dev', namespace: 'team', name: 'knative-backend' }))
32
+ await clickSink({ group: 'serving.knative.dev', kind: 'Service', name: 'group-only-backend' })
33
+ expect(onNavigate).toHaveBeenLastCalledWith(expect.objectContaining({ kind: 'services', group: 'serving.knative.dev', name: 'group-only-backend' }))
34
+ await clickSink({ apiVersion: 'relationships.radar.test/v1', kind: 'Service', name: 'custom-backend' })
35
+ expect(onNavigate).toHaveBeenLastCalledWith(expect.objectContaining({ kind: 'virtualservices', group: 'relationships.radar.test', name: 'custom-backend' }))
36
+ await clickSink({ apiVersion: 'v1', kind: 'Service', name: 'core-backend' })
37
+ expect(onNavigate).toHaveBeenLastCalledWith(expect.objectContaining({ kind: 'services', name: 'core-backend' }))
38
+ expect(onNavigate.mock.lastCall?.[0].group ?? '').toBe('')
39
+
40
+ await act(async () => root.render(<PingSourceRenderer onNavigate={onNavigate} data={{
41
+ metadata: { namespace: 'team' }, spec: { sink: { ref: { kind: 'Service', name: 'unknown-group' } } },
42
+ }} />))
43
+ expect(container.textContent).toContain('unknown-group')
44
+ expect([...container.querySelectorAll('button')].some(button => button.textContent === 'unknown-group')).toBe(false)
45
+
46
+ await act(async () => root.render(<DomainMappingRenderer onNavigate={onNavigate} data={{
47
+ metadata: { namespace: 'team', name: 'example.com' },
48
+ spec: { ref: { apiVersion: 'serving.knative.dev/v1', kind: 'Service', name: 'mapped-backend' } },
49
+ }} />))
50
+ const target = [...container.querySelectorAll('button')].find(button => button.textContent === 'mapped-backend')
51
+ expect(target).toBeDefined()
52
+ await act(async () => target!.click())
53
+ expect(onNavigate).toHaveBeenLastCalledWith(expect.objectContaining({ kind: 'services', group: 'serving.knative.dev', name: 'mapped-backend' }))
54
+ } finally {
55
+ await act(async () => root.unmount())
56
+ resetNavigationMap()
57
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false })
58
+ }
59
+ })
60
+ })
@@ -1,12 +1,12 @@
1
+ import { KnativeReferenceLink } from './KnativeReferenceLink'
1
2
  import { Clock, Server, Container, Link2 } 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 SinkProperty({ sink, ns, onNavigate }: { sink: any; ns: string; onNavigate?: RendererProps['onNavigate'] }) {
@@ -15,10 +15,9 @@ function SinkProperty({ sink, ns, onNavigate }: { sink: any; ns: string; onNavig
15
15
  if (sink.ref) {
16
16
  return (
17
17
  <Property label="Sink" value={
18
- <ResourceLink
19
- name={sink.ref.name}
20
- kind={kindToPlural(sink.ref.kind || 'Service')}
21
- namespace={sink.ref.namespace || ns}
18
+ <KnativeReferenceLink
19
+ reference={sink.ref}
20
+ namespace={ns}
22
21
  onNavigate={onNavigate}
23
22
  />
24
23
  } />
@@ -194,10 +193,9 @@ export function SinkBindingRenderer({ data, onNavigate }: RendererProps) {
194
193
  <Property label="Subject API" value={subjectRef.apiVersion} />
195
194
  {subjectRef.name && (
196
195
  <Property label="Subject" value={
197
- <ResourceLink
198
- name={subjectRef.name}
199
- kind={kindToPlural(subjectRef.kind || 'Deployment')}
200
- namespace={subjectRef.namespace || ns}
196
+ <KnativeReferenceLink
197
+ reference={subjectRef}
198
+ namespace={ns}
201
199
  onNavigate={onNavigate}
202
200
  />
203
201
  } />
@@ -11,7 +11,7 @@ import {
11
11
  isCrossplanePaused,
12
12
  getComposingXRRef,
13
13
  } from '../resource-utils-crossplane'
14
- import { kindToPlural } from '../../../utils/navigation'
14
+ import { kindToPluralWithGroup } from '../../../utils/navigation'
15
15
 
16
16
  interface ManagedResourceRendererProps {
17
17
  data: any
@@ -82,7 +82,7 @@ export function ManagedResourceRenderer({ data, onNavigate }: ManagedResourceRen
82
82
  value={
83
83
  <ResourceLink
84
84
  name={composingXR.name}
85
- kind={kindToPlural(composingXR.kind)}
85
+ kind={kindToPluralWithGroup(composingXR.kind, extractApiGroup(composingXR.apiVersion))}
86
86
  namespace={namespace}
87
87
  group={extractApiGroup(composingXR.apiVersion) || undefined}
88
88
  onNavigate={onNavigate}
@@ -2,16 +2,17 @@ import { Info } from 'lucide-react'
2
2
  import { Tooltip } from '../../ui/Tooltip'
3
3
 
4
4
  interface MetricsUnavailableNoticeProps {
5
+ noUsageYet?: boolean
5
6
  rawError?: string
6
7
  diagnosis?: string
7
8
  }
8
9
 
9
- export function MetricsUnavailableNotice({ rawError, diagnosis }: MetricsUnavailableNoticeProps) {
10
+ export function MetricsUnavailableNotice({ rawError, diagnosis, noUsageYet }: MetricsUnavailableNoticeProps) {
10
11
  return (
11
12
  <div className="card-inner-lg text-xs text-theme-text-tertiary">
12
13
  <div className="flex items-center gap-1.5">
13
14
  <span className="min-w-0 leading-5">
14
- Metrics unavailable. Radar cannot read metrics.k8s.io.
15
+ {noUsageYet ? 'No usage yet: this Pod is not running' : 'Metrics unavailable. Radar cannot read metrics.k8s.io.'}
15
16
  </span>
16
17
  {rawError && (
17
18
  <Tooltip
@@ -450,3 +450,35 @@ describe('PodRenderer on a Radar without variable sources', () => {
450
450
  expect(html).not.toContain('could not be loaded')
451
451
  })
452
452
  })
453
+
454
+ describe('PodRenderer pending Pods, including non-CNPG workloads', () => {
455
+ const pending = { apiVersion: 'v1', kind: 'Pod', metadata: { name: 'nginx-join', namespace: 'default' }, spec: { containers: [{ name: 'nginx', image: 'nginx' }], initContainers: [{ name: 'setup', image: 'busybox' }] }, status: { phase: 'Pending' } }
456
+ const render = (data: any, metricsHistory: any = { containers: [], metricsAPIReachable: true }) => renderToString(<PodRenderer data={data} onCopy={() => {}} copied={null} metricsUnavailable metricsHistory={metricsHistory} />)
457
+ it('shows no usage yet only with a successful metrics API collection', () => {
458
+ expect(render(pending)).toContain('No usage yet: this Pod is not running')
459
+ expect(render(pending)).not.toContain('Radar cannot read metrics.k8s.io')
460
+ expect(render(pending, { containers: [], metricsAPIReachable: false })).toContain('Radar cannot read metrics.k8s.io')
461
+ expect(render(pending, { containers: [], collectionError: 'forbidden' })).toContain('forbidden')
462
+ expect(render({ ...pending, status: { phase: 'Running' } })).toContain('Radar cannot read metrics.k8s.io')
463
+ expect(render({ ...pending, status: { phase: 'Pending', initContainerStatuses: [{ name: 'setup', state: { running: {} } }] } })).not.toContain('No usage yet')
464
+ })
465
+ it('shows unstarted regular and init containers neutrally', () => {
466
+ const html = render(pending)
467
+ expect(html.match(/Not started/g)).toHaveLength(2)
468
+ expect(html).not.toContain('Not Ready')
469
+ expect(html).not.toMatch(/>unknown</)
470
+ })
471
+ it.each(['ContainerCreating', 'PodInitializing', 'ImagePullBackOff', 'CrashLoopBackOff'])('tones reported waiting reason %s appropriately', (reason) => {
472
+ const html = render({ ...pending, status: { phase: 'Pending', containerStatuses: [{ name: 'nginx', state: { waiting: { reason } } }] } })
473
+ const bad = ['ImagePullBackOff', 'CrashLoopBackOff'].includes(reason)
474
+ expect(html).toContain(bad ? 'bg-red-100' : 'bg-theme-hover/50')
475
+ if (bad) expect(html).toContain(reason)
476
+ })
477
+ })
478
+
479
+ it('shows reported running-but-unready nginx readiness as degraded', () => {
480
+ const data = { ...pod, status: { phase: 'Running', containerStatuses: [{ name: pod.spec.containers[0].name, ready: false, state: { running: {} } }] } }
481
+ const html = renderToString(<PodRenderer data={data} onCopy={() => {}} copied={null} />)
482
+ expect(html).toMatch(/bg-amber-100[^>]*>Not Ready</)
483
+ expect(html).toMatch(/>running</)
484
+ })
@@ -5,7 +5,7 @@ import { PolicySection } from './PolicySection'
5
5
  import type { PolicyResourceResponse } from '../../../types/policy'
6
6
  import { Section, PropertyList, Property, ConditionsSection, CopyHandler, AlertBanner, ResourceLink, useOperationalIssuesShown } from '../../ui/drawer-components'
7
7
  import { formatResources, formatDuration, getPodProblems, getPodPhaseDisplay, healthColors, SEVERITY_DOT_COLOR, getDefaultContainerName } from '../resource-utils'
8
- import { getResourceStatusColor, SEVERITY_BADGE_BORDERED } from '../../../utils/badge-colors'
8
+ import { getResourceStatusColor, SEVERITY_BORDER } from '../../../utils/badge-colors'
9
9
  import {
10
10
  rbacVerbBadgeClass,
11
11
  rbacResourceBadgeClass,
@@ -26,6 +26,7 @@ import { Tooltip } from '../../ui/Tooltip'
26
26
  import { RadarUpgradeNote } from '../../ui/RadarUpgradeNote'
27
27
  import { getRadarUpgradeRequirement, type RadarUpgradeRequirement } from '../../../types/fetch-error'
28
28
  import { MetricsChart } from '../../ui/MetricsChart'
29
+ import { Badge } from '../../ui/Badge'
29
30
  import { MetricsUnavailableNotice } from './MetricsUnavailableNotice'
30
31
  import { ContainerEnvironmentSection } from './ContainerEnvironmentSection'
31
32
  import { formatRelativeAgeTime } from '../../../utils/format'
@@ -85,7 +86,7 @@ interface PodRendererProps {
85
86
  renderPortAction?: (props: { namespace: string; podName: string; port: number; protocol: string; disabled?: boolean }) => ReactNode
86
87
  // Metrics data injection
87
88
  metrics?: { containers?: any[]; timestamp?: string }
88
- metricsHistory?: { containers?: any[]; collectionError?: string; metricsUnavailableReason?: string; metricsUnavailableDiagnosis?: string }
89
+ metricsHistory?: { metricsAPIReachable?: boolean; containers?: any[]; collectionError?: string; metricsUnavailableReason?: string; metricsUnavailableDiagnosis?: string }
89
90
  metricsUnavailable?: boolean
90
91
  hideMetricsServer?: boolean
91
92
  // Filesystem browser render props
@@ -305,6 +306,7 @@ export function PodRenderer({
305
306
  const containerStatuses = data.status?.containerStatuses || []
306
307
  const containers = data.spec?.containers || []
307
308
  const initContainers = data.spec?.initContainers || []
309
+ const podResources = data.spec?.resources
308
310
  const initContainerStatuses = data.status?.initContainerStatuses || []
309
311
  const hasEnvironmentDeclarations = [...initContainers, ...containers].some(
310
312
  (container: any) => container.env?.length > 0 || container.envFrom?.length > 0,
@@ -325,7 +327,9 @@ export function PodRenderer({
325
327
  const operationalIssuesShown = useOperationalIssuesShown()
326
328
  const podProblems = getPodProblems(data)
327
329
  const hasProblems = podProblems.length > 0 && !operationalIssuesShown
328
- const showMetricsUnavailable = !!metricsUnavailable && !metricsHistory?.collectionError
330
+ const notRunning = data.status?.phase === 'Pending' && ![...containerStatuses, ...initContainerStatuses].some((s: any) => s.state?.running)
331
+ const noUsageYet = notRunning && metricsHistory?.metricsAPIReachable === true && !metricsHistory.collectionError && !metricsHistory.containers?.length && !metrics?.containers?.length
332
+ const showMetricsUnavailable = !!metricsUnavailable && !metricsHistory?.collectionError && !noUsageYet
329
333
  const hasMetricsHistory = !!metricsHistory?.containers?.length
330
334
  const currentMetrics = metricsUnavailable ? undefined : metrics
331
335
 
@@ -520,10 +524,11 @@ export function PodRenderer({
520
524
  } else if (isWaiting) {
521
525
  statusLabel = state?.waiting?.reason || 'Waiting'
522
526
  } else {
523
- statusLabel = 'Pending'
527
+ statusLabel = 'Not started'
524
528
  }
529
+ const reportedFailure = isFailed || !!(isWaiting && state?.waiting?.reason && !['ContainerCreating', 'PodInitializing'].includes(state.waiting.reason))
525
530
  const statusColor = getResourceStatusColor(
526
- isCompleted ? 'succeeded' : isFailed ? 'failed' : isInitRunning ? 'running' : isWaiting ? 'waiting' : 'pending'
531
+ isCompleted ? 'succeeded' : reportedFailure ? 'failed' : isInitRunning ? 'running' : 'unknown'
527
532
  )
528
533
 
529
534
  // Build command string
@@ -534,10 +539,11 @@ export function PodRenderer({
534
539
  return (
535
540
  <div key={container.name} className={clsx(
536
541
  'rounded-lg p-3 border-l-2',
537
- isCompleted ? 'bg-theme-elevated/20 border-green-500/40' :
538
- isFailed ? 'bg-theme-elevated/30 border-red-500/50' :
539
- isInitRunning ? 'bg-theme-elevated/30 border-blue-500/50' :
540
- 'bg-theme-elevated/30 border-yellow-500/40'
542
+ isCompleted ? 'bg-theme-elevated/20' : 'bg-theme-elevated/30',
543
+ isCompleted ? SEVERITY_BORDER.success :
544
+ reportedFailure ? 'border-l-semantic-error' :
545
+ isInitRunning ? SEVERITY_BORDER.info :
546
+ SEVERITY_BORDER.neutral
541
547
  )}>
542
548
  <div className="flex items-center justify-between mb-2">
543
549
  <div className="flex items-center gap-2">
@@ -575,7 +581,7 @@ export function PodRenderer({
575
581
  </div>
576
582
  )}
577
583
  {/* Waiting reason detail */}
578
- {isWaiting && state?.waiting?.reason && state.waiting.reason !== 'PodInitializing' && (
584
+ {isWaiting && state?.waiting?.reason && !['ContainerCreating', 'PodInitializing'].includes(state.waiting.reason) && (
579
585
  <div className="text-red-400 flex items-center gap-1">
580
586
  <span className="font-medium">{state.waiting.reason}</span>
581
587
  {state.waiting.message && (
@@ -623,6 +629,20 @@ export function PodRenderer({
623
629
 
624
630
  <Section title="Containers" icon={HardDrive} defaultExpanded>
625
631
  <div className="space-y-3">
632
+ {/* Pod-level budget (spec.resources) is shared by all containers, so it
633
+ stays its own row rather than being merged into the container rows. */}
634
+ {(podResources?.requests || podResources?.limits) && (
635
+ <div className="rounded-[10px] border border-dashed border-theme-border p-3">
636
+ <div className="flex items-baseline justify-between gap-2 mb-2">
637
+ <span className="text-sm font-medium text-theme-text-primary">Pod (aggregate)</span>
638
+ <span className="text-xs text-theme-text-tertiary">Shared by all containers</span>
639
+ </div>
640
+ <div className="text-xs text-theme-text-secondary space-y-1">
641
+ {podResources.requests && <div>Requests: {formatResources(podResources.requests)}</div>}
642
+ {podResources.limits && <div>Limits: {formatResources(podResources.limits)}</div>}
643
+ </div>
644
+ </div>
645
+ )}
626
646
  {containers.map((container: any) => {
627
647
  const status = containerStatuses.find((s: any) => s.name === container.name)
628
648
  const state = status?.state
@@ -638,6 +658,7 @@ export function PodRenderer({
638
658
  // failure — tone its badges/text sky, not red, so the drawer agrees
639
659
  // with the calm "Completed" table badge instead of screaming red.
640
660
  const terminatedOk = currentTerminated?.exitCode === 0
661
+ const reportedFailure = !!((currentTerminated?.exitCode != null && currentTerminated.exitCode !== 0) || (currentWaiting?.reason && !['ContainerCreating', 'PodInitializing'].includes(currentWaiting.reason)))
641
662
 
642
663
  return (
643
664
  <div key={container.name} className="card-inner-lg">
@@ -674,23 +695,12 @@ export function PodRenderer({
674
695
  </button>
675
696
  </Tooltip>
676
697
  )}
677
- <span className={clsx(
678
- 'badge',
679
- isReady ? SEVERITY_BADGE_BORDERED.success :
680
- terminatedOk ? SEVERITY_BADGE_BORDERED.info :
681
- SEVERITY_BADGE_BORDERED.error
682
- )}>
683
- {isReady ? 'Ready' : terminatedOk ? 'Completed' : 'Not Ready'}
684
- </span>
685
- <span className={clsx(
686
- 'badge',
687
- stateKey === 'running' ? SEVERITY_BADGE_BORDERED.success :
688
- stateKey === 'waiting' ? SEVERITY_BADGE_BORDERED.warning :
689
- terminatedOk ? SEVERITY_BADGE_BORDERED.info :
690
- SEVERITY_BADGE_BORDERED.error
691
- )}>
698
+ <Badge severity={isReady ? 'success' : terminatedOk ? 'info' : reportedFailure ? 'error' : stateKey === 'running' && isReady === false ? 'warning' : 'neutral'}>
699
+ {isReady ? 'Ready' : terminatedOk ? 'Completed' : !stateKey || stateKey === 'unknown' ? 'Not started' : 'Not Ready'}
700
+ </Badge>
701
+ {stateKey !== 'unknown' && <Badge severity={stateKey === 'running' ? 'success' : terminatedOk ? 'info' : reportedFailure ? 'error' : 'neutral'}>
692
702
  {stateKey}
693
- </span>
703
+ </Badge>}
694
704
  </div>
695
705
  </div>
696
706
  <div className="text-xs text-theme-text-secondary space-y-1">
@@ -704,7 +714,7 @@ export function PodRenderer({
704
714
  )
705
715
  })()}
706
716
  {/* Show current waiting reason (e.g., CrashLoopBackOff) */}
707
- {currentWaiting?.reason && currentWaiting.reason !== 'ContainerCreating' && (
717
+ {currentWaiting?.reason && !['ContainerCreating', 'PodInitializing'].includes(currentWaiting.reason) && (
708
718
  <div className="text-red-400 flex items-center gap-1">
709
719
  <span className="font-medium">{currentWaiting.reason}</span>
710
720
  {currentWaiting.message && (
@@ -831,8 +841,9 @@ export function PodRenderer({
831
841
  )}
832
842
 
833
843
  {/* Resource Usage (from metrics-server) — hidden when Prometheus has CPU/memory data */}
834
- {!hideMetricsServer && !!(currentMetrics?.containers?.length || hasMetricsHistory || metricsHistory?.collectionError || showMetricsUnavailable) && (
844
+ {!hideMetricsServer && !!(currentMetrics?.containers?.length || hasMetricsHistory || metricsHistory?.collectionError || showMetricsUnavailable || noUsageYet) && (
835
845
  <Section title="Resource Usage" icon={Activity} defaultExpanded>
846
+ {noUsageYet && <MetricsUnavailableNotice noUsageYet />}
836
847
  {showMetricsUnavailable && (
837
848
  <MetricsUnavailableNotice rawError={metricsHistory?.metricsUnavailableReason} diagnosis={metricsHistory?.metricsUnavailableDiagnosis} />
838
849
  )}
@@ -852,7 +863,8 @@ export function PodRenderer({
852
863
  // fall through to them or their chart shows no limit line.
853
864
  const containerSpec = containers.find((c: any) => c.name === historyContainer.name)
854
865
  || initContainers.find((c: any) => c.name === historyContainer.name && c.restartPolicy === 'Always')
855
- const limits = containerSpec?.resources?.limits
866
+ // A container without its own limit is still bounded by the pod-level one.
867
+ const limits = { ...podResources?.limits, ...containerSpec?.resources?.limits }
856
868
  const requests = containerSpec?.resources?.requests
857
869
 
858
870
  // Get historical data points (from history or empty)
@@ -6,7 +6,7 @@ import { gatewayBackendResourceRef, gatewayParentResourceRef } from '../../../ut
6
6
 
7
7
  interface SimpleRouteRendererProps {
8
8
  data: any
9
- kind: 'TCPRoute' | 'TLSRoute'
9
+ kind: 'TCPRoute' | 'TLSRoute' | 'UDPRoute'
10
10
  onNavigate?: (ref: ResourceRef) => void
11
11
  }
12
12