@skyhook-io/k8s-ui 1.16.3 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/package.json +3 -3
- package/src/components/applications/ApplicationDetail.test.tsx +15 -0
- package/src/components/applications/ApplicationDetail.tsx +17 -15
- 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/checks/types.ts +2 -13
- 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/GitOpsTableView.tsx +9 -24
- package/src/components/gitops/detail-helpers.ts +1 -1
- 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 +29 -83
- package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +40 -60
- 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 +14 -3
- package/src/components/issues/issues.test.ts +50 -2
- 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.notice.test.tsx +59 -0
- package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +44 -10
- package/src/components/logs/useLogBuffer.test.ts +8 -0
- package/src/components/logs/useLogStream.ts +28 -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 +309 -117
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/renderers/AnalysisRunRenderer.tsx +3 -3
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +6 -5
- package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +5 -4
- package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
- package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +2 -2
- 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 +7 -8
- 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 +9 -17
- package/src/components/resources/renderers/CNPGShared.tsx +2 -2
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
- 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/ConfigAuditReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
- package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
- package/src/components/resources/renderers/CronJobRenderer.tsx +6 -4
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +3 -2
- package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +62 -0
- package/src/components/resources/renderers/EndpointSliceRenderer.tsx +15 -14
- package/src/components/resources/renderers/EventRenderer.tsx +3 -3
- package/src/components/resources/renderers/ExperimentRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +4 -4
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +11 -24
- package/src/components/resources/renderers/GatewayRouteReferences.test.tsx +63 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/HPARenderer.tsx +57 -17
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +12 -25
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/IngressClassReferences.test.tsx +52 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +10 -3
- package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +36 -5
- 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/KarpenterNodeClaimRenderer.tsx +2 -14
- package/src/components/resources/renderers/KedaCredentials.test.tsx +85 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +15 -13
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +6 -3
- 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/KustomizationRenderer.tsx +2 -2
- package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +3 -2
- package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +4 -3
- package/src/components/resources/renderers/LeaseRenderer.tsx +3 -15
- package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
- package/src/components/resources/renderers/NamespaceRenderer.test.ts +69 -2
- package/src/components/resources/renderers/NamespaceRenderer.tsx +6 -1
- package/src/components/resources/renderers/NodeRenderer.test.tsx +81 -1
- package/src/components/resources/renderers/NodeRenderer.tsx +56 -15
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/PodClassReferences.test.tsx +42 -0
- package/src/components/resources/renderers/PodNomination.test.tsx +57 -0
- package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/PodRenderer.tsx +62 -36
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +1 -1
- package/src/components/resources/renderers/RolloutRenderer.tsx +3 -2
- package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/SecretRenderer.test.tsx +88 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +34 -5
- package/src/components/resources/renderers/ServiceRenderer.test.tsx +43 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +11 -3
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +9 -22
- package/src/components/resources/renderers/VPARenderer.tsx +17 -11
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +5 -7
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +2 -2
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +2 -2
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowRenderer.tsx +4 -3
- package/src/components/resources/renderers/kyverno-modern-cells.tsx +1 -1
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +2 -2
- 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 -24
- 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-keda.ts +1 -7
- package/src/components/resources/resource-utils-knative.ts +18 -0
- package/src/components/resources/resource-utils-kyverno.ts +1 -7
- package/src/components/resources/resource-utils-velero.ts +0 -12
- package/src/components/resources/resource-utils.ts +99 -39
- 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/DrainPlanDialog.tsx +1 -0
- package/src/components/shared/EditableYamlView.test.tsx +7 -0
- package/src/components/shared/EditableYamlView.tsx +1 -1
- package/src/components/shared/GitOpsWriteWarning.test.tsx +86 -0
- package/src/components/shared/GitOpsWriteWarning.tsx +135 -0
- package/src/components/shared/ResourceActionsBar.tsx +20 -24
- package/src/components/shared/ResourceRendererDispatch.tsx +13 -10
- package/src/components/shared/SetImageDialog.test.ts +75 -6
- package/src/components/shared/SetImageDialog.tsx +92 -84
- package/src/components/shared/index.ts +4 -1
- package/src/components/timeline/TimelineList.tsx +2 -15
- package/src/components/timeline/TimelineSwimlanes.tsx +3 -11
- 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 +8 -74
- 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/ScheduleValue.test.tsx +5 -0
- package/src/components/ui/ScheduleValue.tsx +3 -2
- package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
- package/src/components/ui/drawer-components.test.tsx +175 -1
- package/src/components/ui/drawer-components.tsx +103 -36
- 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 +101 -0
- package/src/components/workload/WorkloadView.deletedDialog.test.tsx +45 -0
- package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
- package/src/components/workload/WorkloadView.secretSave.test.tsx +62 -0
- package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
- package/src/components/workload/WorkloadView.tsx +297 -104
- 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 +21 -2
- package/src/types/core.ts +60 -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 +49 -2
- package/src/utils/format.ts +9 -1
- package/src/utils/gateway-references.test.ts +17 -0
- package/src/utils/gateway-references.ts +29 -0
- package/src/utils/gitops-owner.ts +3 -2
- package/src/utils/gitops-write-guard.test.ts +401 -0
- package/src/utils/gitops-write-guard.ts +509 -0
- package/src/utils/grant.test.ts +19 -0
- package/src/utils/grant.ts +35 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/ingress-class-references.ts +22 -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 +70 -0
- package/src/utils/navigation.ts +91 -8
- package/src/utils/node-lifecycle.test.ts +36 -0
- package/src/utils/node-lifecycle.ts +77 -0
- package/src/utils/object-reference-navigation.test.ts +43 -0
- package/src/utils/resource-hierarchy.test.ts +69 -0
- package/src/utils/resource-hierarchy.ts +41 -7
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/resource-refs.test.ts +23 -0
- package/src/utils/resource-refs.ts +30 -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
|
@@ -4,6 +4,9 @@ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from
|
|
|
4
4
|
import { MetricsChart } from '../../ui/MetricsChart'
|
|
5
5
|
import { formatMemoryString } from '../../../utils/format'
|
|
6
6
|
import { getExtendedCapacityRows } from '../../../utils/extended-resources'
|
|
7
|
+
import { getNodeLifecycle } from '../../../utils/node-lifecycle'
|
|
8
|
+
import { formatRelativeAgeTime } from '../../../utils/format'
|
|
9
|
+
import type { DrainPlan } from '../../shared/DrainPlanDialog'
|
|
7
10
|
import type { MetricsDataPoint } from '../../../types/core'
|
|
8
11
|
import { MetricsUnavailableNotice } from './MetricsUnavailableNotice'
|
|
9
12
|
|
|
@@ -15,6 +18,9 @@ interface NodeRendererProps {
|
|
|
15
18
|
metricsHistory?: { dataPoints?: MetricsDataPoint[]; collectionError?: string; metricsUnavailableReason?: string; metricsUnavailableDiagnosis?: string }
|
|
16
19
|
metricsUnavailable?: boolean
|
|
17
20
|
hideMetricsServer?: boolean
|
|
21
|
+
removalPlan?: DrainPlan
|
|
22
|
+
removalPlanLoading?: boolean
|
|
23
|
+
removalPlanError?: string
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
// Helper to handle undefined values
|
|
@@ -31,18 +37,18 @@ function formatStorage(value: string | undefined): string {
|
|
|
31
37
|
// Extract genuine problems from node status. Cordoned (unschedulable) is
|
|
32
38
|
// deliberately NOT included here — it's an intentional operator action
|
|
33
39
|
// (cordon/drain), surfaced separately as a calm advisory, not a red error.
|
|
34
|
-
function getNodeProblems(data: any): string[] {
|
|
40
|
+
function getNodeProblems(data: any, lifecycle: ReturnType<typeof getNodeLifecycle>): string[] {
|
|
35
41
|
const problems: string[] = []
|
|
36
42
|
const conditions = data.status?.conditions || []
|
|
37
43
|
|
|
38
44
|
for (const cond of conditions) {
|
|
39
45
|
// NotReady is a problem when status is not True
|
|
40
|
-
if (cond.type === 'Ready' &&
|
|
46
|
+
if (cond.type === 'Ready' && lifecycle.readinessFailed) {
|
|
41
47
|
problems.push(`Node is NotReady${cond.message ? ': ' + cond.message : ''}`)
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
// These conditions are problems when True
|
|
45
|
-
if (cond.status === 'True') {
|
|
51
|
+
if (cond.status === 'True' && lifecycle.problems.includes(cond.type)) {
|
|
46
52
|
if (cond.type === 'DiskPressure') {
|
|
47
53
|
problems.push(`Disk pressure${cond.message ? ': ' + cond.message : ''}`)
|
|
48
54
|
}
|
|
@@ -61,7 +67,7 @@ function getNodeProblems(data: any): string[] {
|
|
|
61
67
|
return problems
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
export function NodeRenderer({ data, relationships, onViewPods, metrics, metricsHistory, metricsUnavailable, hideMetricsServer }: NodeRendererProps) {
|
|
70
|
+
export function NodeRenderer({ data, relationships, onViewPods, metrics, metricsHistory, metricsUnavailable, hideMetricsServer, removalPlan, removalPlanLoading, removalPlanError }: NodeRendererProps) {
|
|
65
71
|
const status = data.status || {}
|
|
66
72
|
const spec = data.spec || {}
|
|
67
73
|
const metadata = data.metadata || {}
|
|
@@ -73,9 +79,14 @@ export function NodeRenderer({ data, relationships, onViewPods, metrics, metrics
|
|
|
73
79
|
const taints = spec.taints || []
|
|
74
80
|
|
|
75
81
|
// Check for problems
|
|
76
|
-
const
|
|
82
|
+
const lifecycle = getNodeLifecycle(data)
|
|
83
|
+
const problems = getNodeProblems(data, lifecycle)
|
|
77
84
|
const hasProblems = problems.length > 0
|
|
78
85
|
const isCordoned = !!spec.unschedulable
|
|
86
|
+
const currentPlan = !removalPlanError && removalPlan?.node === metadata.name ? removalPlan : undefined
|
|
87
|
+
const possibleBlockers = currentPlan?.pods.filter(pod => pod.outcome === 'may-block') ?? []
|
|
88
|
+
const terminatingPods = currentPlan?.pods.filter(pod => pod.terminating).length
|
|
89
|
+
const terminationKnown = currentPlan?.pods.every(pod => pod.terminating !== undefined)
|
|
79
90
|
const showMetricsUnavailable = !!metricsUnavailable && !metricsHistory?.collectionError
|
|
80
91
|
const hasMetricsHistory = !!metricsHistory?.dataPoints?.length
|
|
81
92
|
const currentMetrics = metricsUnavailable ? undefined : metrics
|
|
@@ -92,20 +103,50 @@ export function NodeRenderer({ data, relationships, onViewPods, metrics, metrics
|
|
|
92
103
|
<>
|
|
93
104
|
{/* Problems alert - shown at top when there are genuine issues */}
|
|
94
105
|
{hasProblems && (
|
|
95
|
-
<AlertBanner variant=
|
|
106
|
+
<AlertBanner variant={lifecycle.level === 'unhealthy' ? 'error' : 'warning'} title="Issues Detected" items={problems} />
|
|
96
107
|
)}
|
|
97
108
|
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
{lifecycle.removing ? (
|
|
110
|
+
<AlertBanner
|
|
111
|
+
variant={lifecycle.level === 'unhealthy' ? 'error' : lifecycle.level === 'degraded' ? 'warning' : 'info'}
|
|
112
|
+
title={lifecycle.delayed ? `${lifecycle.label} — taking longer than expected` : lifecycle.label}
|
|
113
|
+
message={lifecycle.delayed
|
|
114
|
+
? 'Check remaining pods, disruption budgets and controller events. Do not uncordon during removal.'
|
|
115
|
+
: 'This node is marked for removal. New pods are kept off it while the controller prepares or completes removal.'}
|
|
116
|
+
>
|
|
117
|
+
<div className="mt-2 space-y-2 text-xs">
|
|
118
|
+
<p>{lifecycle.startedAt !== undefined ? `Marked for removal ${formatRelativeAgeTime(new Date(lifecycle.startedAt).toISOString())}.` : 'Removal start time is not reported.'}</p>
|
|
119
|
+
{removalPlanLoading && <p>Checking remaining pods and disruption budgets…</p>}
|
|
120
|
+
{removalPlanError && <p>Drain details unavailable: {removalPlanError}</p>}
|
|
121
|
+
{!removalPlanLoading && !removalPlanError && !currentPlan && <p>Pod and disruption-budget checks are not available from this host.</p>}
|
|
122
|
+
{currentPlan && (
|
|
123
|
+
<>
|
|
124
|
+
<p>{currentPlan.pods.length} pods in the current snapshot · {terminationKnown ? `${terminatingPods} terminating` : 'termination state unavailable from this Radar'} · {currentPlan.summary.skip} skipped by drain estimate (DaemonSet, static or completed).</p>
|
|
125
|
+
<p className="text-theme-text-secondary">Checked {formatRelativeAgeTime(currentPlan.generatedAt)}. This is a snapshot, not an eviction history. Disruption-budget checks estimate what the Eviction API would allow now.</p>
|
|
126
|
+
<p className="text-theme-text-secondary">Removal controllers can also hold pods marked karpenter.sh/do-not-disrupt or cluster-autoscaler.kubernetes.io/safe-to-evict=false. Those policies are not evaluated by this drain estimate.</p>
|
|
127
|
+
<ul className="max-h-48 overflow-y-auto space-y-1">
|
|
128
|
+
{currentPlan.pods.map(pod => (
|
|
129
|
+
<li key={`${pod.namespace}/${pod.name}`} className="flex justify-between gap-2">
|
|
130
|
+
<span className="break-all font-mono">{pod.namespace}/{pod.name}</span>
|
|
131
|
+
<span className="shrink-0 text-theme-text-secondary">{pod.terminating ? 'Terminating' : pod.outcome === 'may-block' ? 'Budget may block' : pod.outcome === 'skip' ? 'Skipped by drain estimate' : 'Remaining'}</span>
|
|
132
|
+
</li>
|
|
133
|
+
))}
|
|
134
|
+
</ul>
|
|
135
|
+
{!currentPlan.pdbsEvaluated && <p>Disruption-budget checks are incomplete: {currentPlan.pdbError}</p>}
|
|
136
|
+
{possibleBlockers.length > 0 && (
|
|
137
|
+
<AlertBanner variant="warning" title="Disruption budgets may block removal" items={possibleBlockers.map(pod => `${pod.namespace}/${pod.name}: ${pod.reason}`)} />
|
|
138
|
+
)}
|
|
139
|
+
</>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
</AlertBanner>
|
|
143
|
+
) : isCordoned ? (
|
|
103
144
|
<AlertBanner
|
|
104
145
|
variant="warning"
|
|
105
|
-
title="Cordoned
|
|
106
|
-
message="New pods won't be scheduled here.
|
|
146
|
+
title="Cordoned"
|
|
147
|
+
message="New pods won't be scheduled here. The node may be under maintenance; who cordoned it is not recorded. Resume scheduling only after confirming maintenance has finished."
|
|
107
148
|
/>
|
|
108
|
-
)}
|
|
149
|
+
) : null}
|
|
109
150
|
|
|
110
151
|
{/* Node Info */}
|
|
111
152
|
<Section title="Node Info" icon={Server}>
|
|
@@ -314,7 +355,7 @@ export function NodeRenderer({ data, relationships, onViewPods, metrics, metrics
|
|
|
314
355
|
)}
|
|
315
356
|
|
|
316
357
|
{/* Conditions */}
|
|
317
|
-
<ConditionsSection conditions={status.conditions} />
|
|
358
|
+
<ConditionsSection conditions={status.conditions} getConditionTone={condition => condition.type === 'Ready' && lifecycle.removing && !lifecycle.readinessFailed && condition.status !== 'True' ? 'unknown' : undefined} />
|
|
318
359
|
</>
|
|
319
360
|
)
|
|
320
361
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Package, CheckCircle2 } from 'lucide-react'
|
|
2
2
|
import { Section, PropertyList, Property, ConditionsSection, ProblemAlerts } from '../../ui/drawer-components'
|
|
3
|
-
import { formatAge } from '../resource-utils'
|
|
4
3
|
import { formatBytes } from '../../../utils/format'
|
|
5
4
|
import { GitOpsStatusBadge, SyncCountdown } from '../../gitops'
|
|
6
5
|
import { fluxConditionsToGitOpsStatus, type FluxCondition } from '../../../types/gitops'
|
|
6
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
7
7
|
|
|
8
8
|
interface OCIRepositoryRendererProps {
|
|
9
9
|
data: any
|
|
@@ -80,7 +80,7 @@ export function OCIRepositoryRenderer({ data }: OCIRepositoryRendererProps) {
|
|
|
80
80
|
<Property label="Digest" value={artifact.digest} />
|
|
81
81
|
<Property
|
|
82
82
|
label="Last Updated"
|
|
83
|
-
value={
|
|
83
|
+
value={<TimeValue timestamp={artifact.lastUpdateTime} />}
|
|
84
84
|
/>
|
|
85
85
|
{artifact.size && (
|
|
86
86
|
<Property label="Size" value={formatBytes(artifact.size)} />
|
|
@@ -103,7 +103,7 @@ export function OCIRepositoryRenderer({ data }: OCIRepositoryRendererProps) {
|
|
|
103
103
|
{status.lastHandledReconcileAt && (
|
|
104
104
|
<Property
|
|
105
105
|
label="Last Reconciled"
|
|
106
|
-
value={
|
|
106
|
+
value={<TimeValue timestamp={status.lastHandledReconcileAt} />}
|
|
107
107
|
/>
|
|
108
108
|
)}
|
|
109
109
|
</PropertyList>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
6
|
+
import { PodRenderer } from './PodRenderer'
|
|
7
|
+
|
|
8
|
+
const data = {
|
|
9
|
+
metadata: { name: 'worker', namespace: 'app' },
|
|
10
|
+
spec: { containers: [], priority: 1000, priorityClassName: 'urgent', runtimeClassName: 'sandbox' },
|
|
11
|
+
status: { phase: 'Pending' },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('PodRenderer class references', () => {
|
|
15
|
+
it('links explicit class names with their API groups and cluster scope', async () => {
|
|
16
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
|
17
|
+
const container = document.createElement('div')
|
|
18
|
+
const root = createRoot(container)
|
|
19
|
+
const onNavigate = vi.fn()
|
|
20
|
+
try {
|
|
21
|
+
await act(async () => root.render(<PodRenderer data={data} onCopy={() => {}} copied={null} onNavigate={onNavigate} />))
|
|
22
|
+
for (const [name, kind, group] of [
|
|
23
|
+
['urgent', 'priorityclasses', 'scheduling.k8s.io'],
|
|
24
|
+
['sandbox', 'runtimeclasses', 'node.k8s.io'],
|
|
25
|
+
]) {
|
|
26
|
+
const button = [...container.querySelectorAll('button')].find(button => button.textContent === name)
|
|
27
|
+
expect(button).toBeDefined()
|
|
28
|
+
await act(async () => button!.click())
|
|
29
|
+
expect(onNavigate).toHaveBeenLastCalledWith({ kind, group, namespace: '', name })
|
|
30
|
+
}
|
|
31
|
+
} finally {
|
|
32
|
+
await act(async () => root.unmount())
|
|
33
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false })
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('does not infer a class from numeric priority alone', () => {
|
|
38
|
+
const html = renderToStaticMarkup(<PodRenderer data={{ ...data, spec: { containers: [], priority: 1000 } }} onCopy={() => {}} copied={null} />)
|
|
39
|
+
expect(html).not.toContain('Priority Class')
|
|
40
|
+
expect(html).not.toContain('Runtime Class')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
6
|
+
import { PodRenderer } from './PodRenderer'
|
|
7
|
+
|
|
8
|
+
describe('PodRenderer nominated Node', () => {
|
|
9
|
+
it('links the nominated Node of an unbound Pod', async () => {
|
|
10
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
|
11
|
+
const container = document.createElement('div')
|
|
12
|
+
const root = createRoot(container)
|
|
13
|
+
const onNavigate = vi.fn()
|
|
14
|
+
try {
|
|
15
|
+
await act(async () => root.render(
|
|
16
|
+
<PodRenderer
|
|
17
|
+
data={{ metadata: { name: 'pending', namespace: 'app' }, spec: { containers: [] }, status: { phase: 'Pending', nominatedNodeName: 'candidate' } }}
|
|
18
|
+
onCopy={() => {}}
|
|
19
|
+
copied={null}
|
|
20
|
+
onNavigate={onNavigate}
|
|
21
|
+
/>,
|
|
22
|
+
))
|
|
23
|
+
expect(container.textContent).toContain('Nominated Node')
|
|
24
|
+
const button = [...container.querySelectorAll('button')].find(button => button.textContent === 'candidate')
|
|
25
|
+
expect(button).toBeDefined()
|
|
26
|
+
await act(async () => button!.click())
|
|
27
|
+
expect(onNavigate).toHaveBeenCalledWith({ kind: 'nodes', namespace: '', name: 'candidate', group: undefined })
|
|
28
|
+
} finally {
|
|
29
|
+
await act(async () => root.unmount())
|
|
30
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false })
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('hides a nomination on a terminal or terminating unbound Pod', () => {
|
|
35
|
+
for (const data of [
|
|
36
|
+
{ metadata: { name: 'failed', namespace: 'app' }, spec: { containers: [] }, status: { phase: 'Failed', nominatedNodeName: 'stale' } },
|
|
37
|
+
{ metadata: { name: 'deleting', namespace: 'app', deletionTimestamp: '2026-10-07T00:00:00Z' }, spec: { containers: [] }, status: { phase: 'Pending', nominatedNodeName: 'stale' } },
|
|
38
|
+
]) {
|
|
39
|
+
const html = renderToStaticMarkup(<PodRenderer data={data} onCopy={() => {}} copied={null} />)
|
|
40
|
+
expect(html).not.toContain('Nominated Node')
|
|
41
|
+
expect(html).not.toContain('stale')
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('hides a nomination once the Pod is bound, even when it names another node', () => {
|
|
46
|
+
const html = renderToStaticMarkup(
|
|
47
|
+
<PodRenderer
|
|
48
|
+
data={{ metadata: { name: 'running', namespace: 'app' }, spec: { nodeName: 'assigned', containers: [] }, status: { phase: 'Running', nominatedNodeName: 'stale' } }}
|
|
49
|
+
onCopy={() => {}}
|
|
50
|
+
copied={null}
|
|
51
|
+
/>,
|
|
52
|
+
)
|
|
53
|
+
expect(html).not.toContain('Nominated Node')
|
|
54
|
+
expect(html).not.toContain('stale')
|
|
55
|
+
expect(html).toContain('assigned')
|
|
56
|
+
})
|
|
57
|
+
})
|
|
@@ -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,8 +26,10 @@ 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'
|
|
32
|
+
import { formatRelativeAgeTime } from '../../../utils/format'
|
|
31
33
|
|
|
32
34
|
function parseValidDate(dateStr: string): Date | null {
|
|
33
35
|
const d = new Date(dateStr)
|
|
@@ -35,13 +37,7 @@ function parseValidDate(dateStr: string): Date | null {
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
function formatMsAgo(dateStr: string): string | null {
|
|
38
|
-
|
|
39
|
-
if (!d) return null
|
|
40
|
-
const ms = Date.now() - d.getTime()
|
|
41
|
-
if (ms < 60000) return 'just now'
|
|
42
|
-
if (ms < 3600000) return `${Math.floor(ms / 60000)}m ago`
|
|
43
|
-
if (ms < 86400000) return `${Math.floor(ms / 3600000)}h ago`
|
|
44
|
-
return `${Math.floor(ms / 86400000)}d ago`
|
|
40
|
+
return parseValidDate(dateStr) ? formatRelativeAgeTime(dateStr) : null
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
function getRestartRecency(finishedAt: string | undefined, restarts: number): { color: string; label: string | null } {
|
|
@@ -90,7 +86,7 @@ interface PodRendererProps {
|
|
|
90
86
|
renderPortAction?: (props: { namespace: string; podName: string; port: number; protocol: string; disabled?: boolean }) => ReactNode
|
|
91
87
|
// Metrics data injection
|
|
92
88
|
metrics?: { containers?: any[]; timestamp?: string }
|
|
93
|
-
metricsHistory?: { containers?: any[]; collectionError?: string; metricsUnavailableReason?: string; metricsUnavailableDiagnosis?: string }
|
|
89
|
+
metricsHistory?: { metricsAPIReachable?: boolean; containers?: any[]; collectionError?: string; metricsUnavailableReason?: string; metricsUnavailableDiagnosis?: string }
|
|
94
90
|
metricsUnavailable?: boolean
|
|
95
91
|
hideMetricsServer?: boolean
|
|
96
92
|
// Filesystem browser render props
|
|
@@ -310,6 +306,7 @@ export function PodRenderer({
|
|
|
310
306
|
const containerStatuses = data.status?.containerStatuses || []
|
|
311
307
|
const containers = data.spec?.containers || []
|
|
312
308
|
const initContainers = data.spec?.initContainers || []
|
|
309
|
+
const podResources = data.spec?.resources
|
|
313
310
|
const initContainerStatuses = data.status?.initContainerStatuses || []
|
|
314
311
|
const hasEnvironmentDeclarations = [...initContainers, ...containers].some(
|
|
315
312
|
(container: any) => container.env?.length > 0 || container.envFrom?.length > 0,
|
|
@@ -330,7 +327,9 @@ export function PodRenderer({
|
|
|
330
327
|
const operationalIssuesShown = useOperationalIssuesShown()
|
|
331
328
|
const podProblems = getPodProblems(data)
|
|
332
329
|
const hasProblems = podProblems.length > 0 && !operationalIssuesShown
|
|
333
|
-
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
|
|
334
333
|
const hasMetricsHistory = !!metricsHistory?.containers?.length
|
|
335
334
|
const currentMetrics = metricsUnavailable ? undefined : metrics
|
|
336
335
|
|
|
@@ -453,6 +452,25 @@ export function PodRenderer({
|
|
|
453
452
|
<Property label="Node" value={
|
|
454
453
|
data.spec?.nodeName ? <ResourceLink name={data.spec.nodeName} kind="nodes" onNavigate={onNavigate} /> : undefined
|
|
455
454
|
} copyable onCopy={onCopy} copied={copied} />
|
|
455
|
+
{/* A nomination outlives its meaning: the API server clears it on binding by
|
|
456
|
+
default only from Kubernetes 1.35, and pod GC fails a terminating unbound
|
|
457
|
+
Pod without clearing it. Only a Pending, unbound, live Pod can still use it. */}
|
|
458
|
+
{data.status?.nominatedNodeName && !data.spec?.nodeName && data.status?.phase === 'Pending' && !data.metadata?.deletionTimestamp && (
|
|
459
|
+
<Property
|
|
460
|
+
label={
|
|
461
|
+
<Tooltip content="The node this Pod is expected to run on, set by the scheduler or another component. It can change before the Pod is bound." position="right">
|
|
462
|
+
<span className="border-b border-dotted border-theme-text-tertiary cursor-help">Nominated Node</span>
|
|
463
|
+
</Tooltip>
|
|
464
|
+
}
|
|
465
|
+
value={<ResourceLink name={data.status.nominatedNodeName} kind="nodes" onNavigate={onNavigate} />}
|
|
466
|
+
/>
|
|
467
|
+
)}
|
|
468
|
+
{data.spec?.priorityClassName && <Property label="Priority Class" value={
|
|
469
|
+
<ResourceLink name={data.spec.priorityClassName} kind="priorityclasses" group="scheduling.k8s.io" onNavigate={onNavigate} />
|
|
470
|
+
} />}
|
|
471
|
+
{data.spec?.runtimeClassName && <Property label="Runtime Class" value={
|
|
472
|
+
<ResourceLink name={data.spec.runtimeClassName} kind="runtimeclasses" group="node.k8s.io" onNavigate={onNavigate} />
|
|
473
|
+
} />}
|
|
456
474
|
<Property label="Pod IP" value={data.status?.podIP} copyable onCopy={onCopy} copied={copied} />
|
|
457
475
|
<Property label="Host IP" value={data.status?.hostIP} />
|
|
458
476
|
<Property
|
|
@@ -506,10 +524,11 @@ export function PodRenderer({
|
|
|
506
524
|
} else if (isWaiting) {
|
|
507
525
|
statusLabel = state?.waiting?.reason || 'Waiting'
|
|
508
526
|
} else {
|
|
509
|
-
statusLabel = '
|
|
527
|
+
statusLabel = 'Not started'
|
|
510
528
|
}
|
|
529
|
+
const reportedFailure = isFailed || !!(isWaiting && state?.waiting?.reason && !['ContainerCreating', 'PodInitializing'].includes(state.waiting.reason))
|
|
511
530
|
const statusColor = getResourceStatusColor(
|
|
512
|
-
isCompleted ? 'succeeded' :
|
|
531
|
+
isCompleted ? 'succeeded' : reportedFailure ? 'failed' : isInitRunning ? 'running' : 'unknown'
|
|
513
532
|
)
|
|
514
533
|
|
|
515
534
|
// Build command string
|
|
@@ -520,10 +539,11 @@ export function PodRenderer({
|
|
|
520
539
|
return (
|
|
521
540
|
<div key={container.name} className={clsx(
|
|
522
541
|
'rounded-lg p-3 border-l-2',
|
|
523
|
-
isCompleted ? 'bg-theme-elevated/20
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
|
527
547
|
)}>
|
|
528
548
|
<div className="flex items-center justify-between mb-2">
|
|
529
549
|
<div className="flex items-center gap-2">
|
|
@@ -561,7 +581,7 @@ export function PodRenderer({
|
|
|
561
581
|
</div>
|
|
562
582
|
)}
|
|
563
583
|
{/* Waiting reason detail */}
|
|
564
|
-
{isWaiting && state?.waiting?.reason && state.waiting.reason
|
|
584
|
+
{isWaiting && state?.waiting?.reason && !['ContainerCreating', 'PodInitializing'].includes(state.waiting.reason) && (
|
|
565
585
|
<div className="text-red-400 flex items-center gap-1">
|
|
566
586
|
<span className="font-medium">{state.waiting.reason}</span>
|
|
567
587
|
{state.waiting.message && (
|
|
@@ -609,6 +629,20 @@ export function PodRenderer({
|
|
|
609
629
|
|
|
610
630
|
<Section title="Containers" icon={HardDrive} defaultExpanded>
|
|
611
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
|
+
)}
|
|
612
646
|
{containers.map((container: any) => {
|
|
613
647
|
const status = containerStatuses.find((s: any) => s.name === container.name)
|
|
614
648
|
const state = status?.state
|
|
@@ -624,6 +658,7 @@ export function PodRenderer({
|
|
|
624
658
|
// failure — tone its badges/text sky, not red, so the drawer agrees
|
|
625
659
|
// with the calm "Completed" table badge instead of screaming red.
|
|
626
660
|
const terminatedOk = currentTerminated?.exitCode === 0
|
|
661
|
+
const reportedFailure = !!((currentTerminated?.exitCode != null && currentTerminated.exitCode !== 0) || (currentWaiting?.reason && !['ContainerCreating', 'PodInitializing'].includes(currentWaiting.reason)))
|
|
627
662
|
|
|
628
663
|
return (
|
|
629
664
|
<div key={container.name} className="card-inner-lg">
|
|
@@ -660,23 +695,12 @@ export function PodRenderer({
|
|
|
660
695
|
</button>
|
|
661
696
|
</Tooltip>
|
|
662
697
|
)}
|
|
663
|
-
<
|
|
664
|
-
'
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
SEVERITY_BADGE_BORDERED.error
|
|
668
|
-
)}>
|
|
669
|
-
{isReady ? 'Ready' : terminatedOk ? 'Completed' : 'Not Ready'}
|
|
670
|
-
</span>
|
|
671
|
-
<span className={clsx(
|
|
672
|
-
'badge',
|
|
673
|
-
stateKey === 'running' ? SEVERITY_BADGE_BORDERED.success :
|
|
674
|
-
stateKey === 'waiting' ? SEVERITY_BADGE_BORDERED.warning :
|
|
675
|
-
terminatedOk ? SEVERITY_BADGE_BORDERED.info :
|
|
676
|
-
SEVERITY_BADGE_BORDERED.error
|
|
677
|
-
)}>
|
|
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'}>
|
|
678
702
|
{stateKey}
|
|
679
|
-
</
|
|
703
|
+
</Badge>}
|
|
680
704
|
</div>
|
|
681
705
|
</div>
|
|
682
706
|
<div className="text-xs text-theme-text-secondary space-y-1">
|
|
@@ -690,7 +714,7 @@ export function PodRenderer({
|
|
|
690
714
|
)
|
|
691
715
|
})()}
|
|
692
716
|
{/* Show current waiting reason (e.g., CrashLoopBackOff) */}
|
|
693
|
-
{currentWaiting?.reason && currentWaiting.reason
|
|
717
|
+
{currentWaiting?.reason && !['ContainerCreating', 'PodInitializing'].includes(currentWaiting.reason) && (
|
|
694
718
|
<div className="text-red-400 flex items-center gap-1">
|
|
695
719
|
<span className="font-medium">{currentWaiting.reason}</span>
|
|
696
720
|
{currentWaiting.message && (
|
|
@@ -817,8 +841,9 @@ export function PodRenderer({
|
|
|
817
841
|
)}
|
|
818
842
|
|
|
819
843
|
{/* Resource Usage (from metrics-server) — hidden when Prometheus has CPU/memory data */}
|
|
820
|
-
{!hideMetricsServer && !!(currentMetrics?.containers?.length || hasMetricsHistory || metricsHistory?.collectionError || showMetricsUnavailable) && (
|
|
844
|
+
{!hideMetricsServer && !!(currentMetrics?.containers?.length || hasMetricsHistory || metricsHistory?.collectionError || showMetricsUnavailable || noUsageYet) && (
|
|
821
845
|
<Section title="Resource Usage" icon={Activity} defaultExpanded>
|
|
846
|
+
{noUsageYet && <MetricsUnavailableNotice noUsageYet />}
|
|
822
847
|
{showMetricsUnavailable && (
|
|
823
848
|
<MetricsUnavailableNotice rawError={metricsHistory?.metricsUnavailableReason} diagnosis={metricsHistory?.metricsUnavailableDiagnosis} />
|
|
824
849
|
)}
|
|
@@ -838,7 +863,8 @@ export function PodRenderer({
|
|
|
838
863
|
// fall through to them or their chart shows no limit line.
|
|
839
864
|
const containerSpec = containers.find((c: any) => c.name === historyContainer.name)
|
|
840
865
|
|| initContainers.find((c: any) => c.name === historyContainer.name && c.restartPolicy === 'Always')
|
|
841
|
-
|
|
866
|
+
// A container without its own limit is still bounded by the pod-level one.
|
|
867
|
+
const limits = { ...podResources?.limits, ...containerSpec?.resources?.limits }
|
|
842
868
|
const requests = containerSpec?.resources?.requests
|
|
843
869
|
|
|
844
870
|
// Get historical data points (from history or empty)
|
|
@@ -269,7 +269,7 @@ describe('rolloutProblems', () => {
|
|
|
269
269
|
)
|
|
270
270
|
expect(problems).toHaveLength(1)
|
|
271
271
|
expect(problems[0].color).toBe('yellow')
|
|
272
|
-
expect(problems[0].message).toMatch(/^Rollout is paused: CanaryPauseStep \(
|
|
272
|
+
expect(problems[0].message).toMatch(/^Rollout is paused: CanaryPauseStep \(for \d+d\)$/)
|
|
273
273
|
})
|
|
274
274
|
|
|
275
275
|
it('does not report a paused rollout that is actually aborted', () => {
|
|
@@ -9,6 +9,7 @@ import { CanaryStepTimeline, BlueGreenTimeline } from './rollout/CanaryStepTimel
|
|
|
9
9
|
import { ReplicaSetProgression } from './rollout/ReplicaSetProgression'
|
|
10
10
|
import { LookupFailureNote } from './LookupFailureNote'
|
|
11
11
|
import type { WorkloadRevision, WorkloadPodInfo } from '../../../types'
|
|
12
|
+
import { formatRelativeAgeTime } from '../../../utils/format'
|
|
12
13
|
|
|
13
14
|
export type RolloutAction = 'abort' | 'retry' | 'promote' | 'promote-full' | 'skip-step'
|
|
14
15
|
|
|
@@ -214,7 +215,7 @@ export function rolloutProblems(data: any): RolloutProblem[] {
|
|
|
214
215
|
|
|
215
216
|
if (phase === 'Paused' && !status.abort) {
|
|
216
217
|
const reasons = (status.pauseConditions || []).map((pc: any) => {
|
|
217
|
-
const since = pc.startTime ? ` (
|
|
218
|
+
const since = pc.startTime ? ` (for ${formatAge(pc.startTime)})` : ''
|
|
218
219
|
return `${pc.reason || 'Unknown'}${since}`
|
|
219
220
|
})
|
|
220
221
|
problems.push({
|
|
@@ -741,7 +742,7 @@ export function RolloutRenderer({ data, onNavigate, capabilities, onAction, pend
|
|
|
741
742
|
</span>
|
|
742
743
|
)}
|
|
743
744
|
<span className={clsx('text-xs text-theme-text-tertiary', run.metricsTotal === 0 && 'ml-auto')}>
|
|
744
|
-
{
|
|
745
|
+
{formatRelativeAgeTime(run.createdAt)}
|
|
745
746
|
</span>
|
|
746
747
|
</div>
|
|
747
748
|
))}
|
|
@@ -3,8 +3,8 @@ import { Package, Search } from 'lucide-react'
|
|
|
3
3
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
4
4
|
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
5
5
|
import { Input } from '../../ui/Input'
|
|
6
|
-
import { formatAge } from '../resource-utils'
|
|
7
6
|
import { formatTrivyImage } from './trivy-shared'
|
|
7
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
8
8
|
|
|
9
9
|
interface SbomReportRendererProps {
|
|
10
10
|
data: any
|
|
@@ -57,7 +57,7 @@ export function SbomReportRenderer({ data }: SbomReportRendererProps) {
|
|
|
57
57
|
<Property label="Components" value={String(componentsCount)} />
|
|
58
58
|
{depsCount > 0 && <Property label="Dependencies" value={String(depsCount)} />}
|
|
59
59
|
<Property label="Scanner" value={scanner.name ? `${scanner.name} ${scanner.version || ''}`.trim() : '-'} />
|
|
60
|
-
<Property label="Scanned" value={
|
|
60
|
+
<Property label="Scanned" value={<TimeValue timestamp={report.updateTimestamp} />} />
|
|
61
61
|
</PropertyList>
|
|
62
62
|
</Section>
|
|
63
63
|
|