@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.
- package/README.md +41 -0
- package/package.json +3 -3
- package/src/components/charts/AreaChart.interaction.test.tsx +118 -0
- package/src/components/charts/AreaChart.test.tsx +25 -0
- package/src/components/charts/AreaChart.tsx +141 -12
- package/src/components/charts/index.ts +1 -0
- package/src/components/checks/ChecksView.tsx +18 -1
- package/src/components/cnpg/CNPGBackupSummary.tsx +57 -9
- package/src/components/cnpg/CNPGClusterHASection.test.tsx +90 -0
- package/src/components/cnpg/CNPGClusterHASection.tsx +331 -0
- package/src/components/cnpg/CNPGClusterSummary.test.tsx +275 -0
- package/src/components/cnpg/CNPGClusterSummary.tsx +310 -96
- package/src/components/cnpg/CNPGConnectSection.test.tsx +64 -0
- package/src/components/cnpg/CNPGConnectSection.tsx +178 -0
- package/src/components/cnpg/CNPGDeclarativeSummary.tsx +157 -26
- package/src/components/cnpg/CNPGImageCatalogSummary.tsx +7 -4
- package/src/components/cnpg/CNPGLogicalPath.test.tsx +22 -0
- package/src/components/cnpg/CNPGLogicalPath.tsx +150 -0
- package/src/components/cnpg/CNPGObjectStoreSummary.tsx +9 -6
- package/src/components/cnpg/CNPGObjectSummary.test.tsx +193 -2
- package/src/components/cnpg/CNPGPoolerScheduling.test.tsx +36 -0
- package/src/components/cnpg/CNPGPoolerSummary.tsx +241 -14
- package/src/components/cnpg/CNPGSharedSummary.tsx +15 -8
- package/src/components/cnpg/CNPGWALArchivingFact.test.tsx +39 -0
- package/src/components/cnpg/backupRuns.test.ts +17 -0
- package/src/components/cnpg/backupRuns.ts +19 -0
- package/src/components/cnpg/connect.test.ts +124 -0
- package/src/components/cnpg/connect.ts +155 -0
- package/src/components/cnpg/databaseRole.test.ts +38 -0
- package/src/components/cnpg/databaseRole.ts +80 -0
- package/src/components/cnpg/ha.test.ts +446 -0
- package/src/components/cnpg/ha.ts +621 -0
- package/src/components/cnpg/index.ts +18 -0
- package/src/components/cnpg/logicalReplication.test.ts +121 -0
- package/src/components/cnpg/logicalReplication.ts +252 -0
- package/src/components/cnpg/pooler.test.ts +84 -0
- package/src/components/cnpg/pooler.ts +225 -0
- package/src/components/cnpg/primitives.tsx +21 -117
- package/src/components/cnpg/problems.ts +12 -0
- package/src/components/cnpg/relations.test.ts +63 -13
- package/src/components/cnpg/relations.ts +61 -52
- package/src/components/cnpg/schedule.ts +35 -0
- package/src/components/cnpg/workspace-disk.test.ts +97 -0
- package/src/components/cnpg/workspace-fleet-metrics.test.ts +250 -0
- package/src/components/cnpg/workspace-problems.test.ts +175 -0
- package/src/components/cnpg/workspace.test.ts +380 -9
- package/src/components/cnpg/workspace.ts +1137 -136
- package/src/components/dock/DockContext.test.tsx +32 -0
- package/src/components/dock/DockContext.tsx +18 -4
- package/src/components/dock/LocalTerminalTab.test.tsx +40 -0
- package/src/components/dock/LocalTerminalTab.tsx +4 -3
- package/src/components/dock/TerminalTab.tsx +20 -8
- package/src/components/facts/certainty.tsx +36 -0
- package/src/components/facts/facts.test.tsx +24 -0
- package/src/components/facts/facts.tsx +62 -0
- package/src/components/facts/index.ts +6 -0
- package/src/components/facts/managed-by.test.tsx +17 -0
- package/src/components/facts/managed-by.tsx +36 -0
- package/src/components/facts/public-exports.test.tsx +35 -0
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +6 -2
- package/src/components/gitops/GitOpsHealthSourceNotice.test.tsx +38 -0
- package/src/components/gitops/GitOpsHealthSourceNotice.tsx +13 -3
- package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +115 -0
- package/src/components/gitops/insights/ArgoResourceDiff.tsx +22 -5
- package/src/components/gitops/insights/DriftPanel.tsx +62 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +24 -71
- package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +38 -55
- package/src/components/gitops/tree/layout.test.ts +111 -0
- package/src/components/gitops/tree/layout.ts +128 -0
- package/src/components/gitops/tree/tree-helpers.test.ts +17 -0
- package/src/components/gitops/tree/tree-helpers.ts +7 -1
- package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
- package/src/components/issues/IssuesView.tsx +40 -5
- package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
- package/src/components/issues/ResourceIssuesSection.tsx +3 -0
- package/src/components/issues/index.ts +3 -1
- package/src/components/issues/issue-timing.ts +12 -1
- package/src/components/issues/issues.test.ts +49 -1
- package/src/components/issues/severity.ts +21 -0
- package/src/components/issues/types.ts +23 -0
- package/src/components/logs/LogCore.theme.test.tsx +81 -3
- package/src/components/logs/LogCore.tsx +25 -17
- package/src/components/logs/LogToolbarSelects.tsx +3 -1
- package/src/components/logs/LogsViewer.test.tsx +291 -0
- package/src/components/logs/LogsViewer.tsx +72 -11
- package/src/components/logs/StructuredLogLine.test.tsx +76 -0
- package/src/components/logs/StructuredLogLine.tsx +22 -30
- package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +39 -10
- package/src/components/logs/useLogBuffer.test.ts +8 -0
- package/src/components/logs/useLogStream.ts +17 -1
- package/src/components/problems/index.ts +1 -0
- package/src/components/problems/problems.test.tsx +63 -0
- package/src/components/problems/problems.tsx +165 -0
- package/src/components/resources/ActiveFilterBar.test.tsx +78 -0
- package/src/components/resources/ActiveFilterBar.tsx +86 -0
- package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
- package/src/components/resources/ResourcesSidebar.tsx +92 -41
- package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
- package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -0
- package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
- package/src/components/resources/ResourcesView.tsx +302 -109
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +5 -7
- package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +7 -15
- package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
- package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
- package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
- package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
- package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
- package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +39 -1
- package/src/components/resources/renderers/EndpointSliceRenderer.tsx +14 -13
- package/src/components/resources/renderers/HPARenderer.tsx +56 -16
- package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +30 -3
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
- package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +13 -11
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
- package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
- package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
- package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
- package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
- package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/PodRenderer.tsx +41 -29
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -1
- package/src/components/resources/renderers/VPARenderer.tsx +17 -11
- package/src/components/resources/resource-utils-capi.ts +1 -1
- package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
- package/src/components/resources/resource-utils-cnpg.ts +51 -20
- package/src/components/resources/resource-utils-contour.test.ts +44 -0
- package/src/components/resources/resource-utils-contour.ts +16 -0
- package/src/components/resources/resource-utils-istio.ts +12 -8
- package/src/components/resources/resource-utils-knative.ts +18 -0
- package/src/components/resources/resource-utils.ts +82 -6
- package/src/components/resources/rules-column-width.test.ts +1 -1
- package/src/components/resources/summarize-scheduler-message.test.ts +30 -0
- package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
- package/src/components/shared/ActionConfirmDialog.tsx +246 -0
- package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
- package/src/components/shared/CreateResourceDialog.tsx +126 -80
- package/src/components/shared/DetailShell.test.tsx +80 -0
- package/src/components/shared/DetailShell.tsx +85 -10
- package/src/components/shared/EditableYamlView.test.tsx +7 -0
- package/src/components/shared/EditableYamlView.tsx +1 -1
- package/src/components/shared/ResourceActionsBar.tsx +11 -4
- package/src/components/shared/ResourceRendererDispatch.tsx +12 -9
- package/src/components/shared/index.ts +2 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +1 -1
- package/src/components/timeline/timeline-filters.ts +1 -0
- package/src/components/topology/K8sResourceNode.test.ts +18 -1
- package/src/components/topology/K8sResourceNode.tsx +44 -2
- package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
- package/src/components/topology/TopologyGraph.tsx +3 -71
- package/src/components/topology/TopologySearch.tsx +1 -0
- package/src/components/topology/internet-groups.test.ts +35 -0
- package/src/components/topology/internet-groups.ts +55 -0
- package/src/components/topology/layout.ts +1 -1
- package/src/components/ui/Badge.tsx +1 -0
- package/src/components/ui/FoldSection.tsx +68 -0
- package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
- package/src/components/ui/RefLink.tsx +20 -0
- package/src/components/ui/RowActionMenu.tsx +8 -3
- package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
- package/src/components/ui/drawer-components.test.tsx +110 -1
- package/src/components/ui/drawer-components.tsx +62 -9
- package/src/components/ui/index.ts +3 -1
- package/src/components/ui/monacoEditorFeatures.ts +77 -0
- package/src/components/ui/monacoRuntime.ts +14 -19
- package/src/components/ui/status-tone.test.ts +10 -1
- package/src/components/ui/status-tone.tsx +29 -0
- package/src/components/workload/WorkloadView.deleted.test.tsx +43 -0
- package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
- package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
- package/src/components/workload/WorkloadView.tsx +180 -64
- package/src/index.ts +14 -1
- package/src/monaco-deep.d.ts +19 -12
- package/src/theme/components.css +13 -0
- package/src/types/capacity.ts +11 -2
- package/src/types/core.ts +29 -2
- package/src/types/gitops-flux-status.test.ts +32 -1
- package/src/types/gitops-insights.ts +19 -2
- package/src/types/gitops-tree.ts +10 -0
- package/src/types/gitops.ts +26 -6
- package/src/utils/api-resources.ts +7 -0
- package/src/utils/application-history.ts +1 -0
- package/src/utils/cnpg-backup.test.ts +26 -0
- package/src/utils/cnpg-backup.ts +70 -0
- package/src/utils/format.test.ts +9 -0
- package/src/utils/format.ts +4 -0
- package/src/utils/gitops-owner.ts +3 -2
- package/src/utils/grant.test.ts +19 -0
- package/src/utils/grant.ts +35 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/k8s-errors.ts +7 -7
- package/src/utils/log-format.test.ts +41 -0
- package/src/utils/log-format.ts +71 -15
- package/src/utils/log-level.ts +12 -8
- package/src/utils/navigation.test.ts +57 -0
- package/src/utils/navigation.ts +86 -8
- package/src/utils/object-reference-navigation.test.ts +43 -0
- package/src/utils/resource-hierarchy.test.ts +37 -0
- package/src/utils/resource-hierarchy.ts +15 -7
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/skeleton-yaml.test.ts +21 -0
- package/src/utils/skeleton-yaml.ts +16 -0
- package/src/utils/url-path.test.ts +35 -0
- package/src/utils/url-path.ts +21 -0
- package/src/utils/validators.test.ts +4 -13
- package/src/utils/validators.ts +5 -19
- package/src/utils/view-transition.test.ts +35 -1
- 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
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
100
|
-
name
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
<
|
|
251
|
-
|
|
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
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
<
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
<
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
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
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
-
<
|
|
170
|
-
|
|
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
|
-
<
|
|
217
|
-
|
|
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
|
|
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
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
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 {
|
|
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={
|
|
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,
|
|
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
|
|
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 = '
|
|
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' :
|
|
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
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
|
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
|
-
<
|
|
678
|
-
'
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
</
|
|
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
|
|
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
|
-
|
|
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)
|