@skyhook-io/k8s-ui 1.8.11 → 1.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/package.json +4 -4
  2. package/src/components/applications/ApplicationDetail.test.tsx +136 -8
  3. package/src/components/applications/ApplicationDetail.tsx +1215 -487
  4. package/src/components/gitops/GitOpsDetailLayout.tsx +13 -3
  5. package/src/components/gitops/GitOpsTableView.tsx +10 -3
  6. package/src/components/gitops/SyncOptionsDialog.test.ts +48 -0
  7. package/src/components/gitops/SyncOptionsDialog.tsx +189 -45
  8. package/src/components/gitops/insights/ArgoResourceDiff.tsx +172 -0
  9. package/src/components/gitops/insights/GitOpsInsightViews.tsx +201 -21
  10. package/src/components/gitops/insights/index.ts +1 -0
  11. package/src/components/gitops/insights/insights-helpers.test.ts +16 -0
  12. package/src/components/gitops/insights/insights-helpers.ts +5 -0
  13. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +5 -0
  14. package/src/components/issues/IssuesView.tsx +55 -29
  15. package/src/components/issues/ResourceIssuesSection.test.tsx +33 -1
  16. package/src/components/issues/ResourceIssuesSection.tsx +21 -1
  17. package/src/components/issues/severity.ts +1 -0
  18. package/src/components/resources/renderers/AWSMachineRenderer.tsx +3 -2
  19. package/src/components/resources/renderers/AWSManagedClusterRenderer.tsx +3 -2
  20. package/src/components/resources/renderers/AWSManagedControlPlaneRenderer.tsx +3 -2
  21. package/src/components/resources/renderers/AWSManagedMachinePoolRenderer.tsx +3 -2
  22. package/src/components/resources/renderers/AzureMachineRenderer.tsx +3 -2
  23. package/src/components/resources/renderers/AzureManagedControlPlaneRenderer.tsx +3 -2
  24. package/src/components/resources/renderers/AzureManagedMachinePoolRenderer.tsx +3 -2
  25. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +3 -2
  26. package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +3 -2
  27. package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +3 -2
  28. package/src/components/resources/renderers/CAPIMachineHealthCheckRenderer.tsx +3 -2
  29. package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +3 -2
  30. package/src/components/resources/renderers/CAPIMachineRenderer.tsx +3 -2
  31. package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +3 -2
  32. package/src/components/resources/renderers/CertificateRenderer.tsx +3 -2
  33. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +3 -2
  34. package/src/components/resources/renderers/CompositeRenderer.tsx +3 -2
  35. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +3 -2
  36. package/src/components/resources/renderers/GCPMachineRenderer.tsx +3 -2
  37. package/src/components/resources/renderers/GCPManagedControlPlaneRenderer.tsx +3 -2
  38. package/src/components/resources/renderers/GCPManagedMachinePoolRenderer.tsx +3 -2
  39. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +7 -3
  40. package/src/components/resources/renderers/GatewayClassRenderer.tsx +3 -2
  41. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +7 -3
  42. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +3 -2
  43. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +3 -2
  44. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +3 -2
  45. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +3 -2
  46. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +3 -2
  47. package/src/components/resources/renderers/ManagedResourceRenderer.tsx +3 -2
  48. package/src/components/resources/renderers/PVCRenderer.tsx +3 -2
  49. package/src/components/resources/renderers/SealedSecretRenderer.tsx +3 -2
  50. package/src/components/resources/renderers/SecretStoreRenderer.tsx +3 -2
  51. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +7 -3
  52. package/src/components/resources/resource-utils-argo.test.ts +18 -1
  53. package/src/components/resources/resource-utils-argo.ts +8 -0
  54. package/src/components/shared/ResourceActionsBar.tsx +2 -3
  55. package/src/components/shared/UnifiedDiff.tsx +51 -0
  56. package/src/components/shared/index.ts +1 -0
  57. package/src/components/timeline/TimelineList.tsx +7 -3
  58. package/src/components/timeline/TimelineToolbarState.test.tsx +24 -0
  59. package/src/components/topology/TopologyBreadcrumb.tsx +30 -0
  60. package/src/components/topology/TopologyControls.tsx +3 -1
  61. package/src/components/topology/TopologyGraph.tsx +76 -90
  62. package/src/components/topology/TopologyOverlayBar.tsx +30 -0
  63. package/src/components/topology/TopologySearch.tsx +23 -15
  64. package/src/components/topology/index.ts +2 -0
  65. package/src/components/ui/Badge.tsx +1 -1
  66. package/src/components/ui/SelectMenu.tsx +81 -0
  67. package/src/components/ui/drawer-components.tsx +9 -6
  68. package/src/components/ui/index.ts +2 -0
  69. package/src/components/workload/WorkloadView.tsx +24 -3
  70. package/src/theme/tailwind-theme.css +1 -0
  71. package/src/theme/variables.css +1 -0
  72. package/src/types/gitops-insights.ts +69 -1
  73. package/src/types/gitops.ts +3 -3
  74. package/src/utils/application-history.test.ts +538 -0
  75. package/src/utils/application-history.ts +257 -0
  76. package/src/utils/applications.test.ts +34 -0
  77. package/src/utils/applications.ts +25 -0
  78. package/src/utils/index.ts +1 -0
  79. package/src/utils/resource-hierarchy.test.ts +35 -1
  80. package/src/utils/resource-hierarchy.ts +20 -0
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { renderToString } from 'react-dom/server'
3
- import { ResourceIssuesSection } from './ResourceIssuesSection'
3
+ import { ResourceIssuesSection, sameResource } from './ResourceIssuesSection'
4
+ import { IssueRow } from './IssuesView'
4
5
  import type { Issue } from './types'
5
6
 
6
7
  const issue: Issue = {
@@ -37,4 +38,35 @@ describe('ResourceIssuesSection', () => {
37
38
  expect(html).not.toContain('Context')
38
39
  expect(html).not.toContain('Blocked pods')
39
40
  })
41
+
42
+ it('shows the Subject deep-link by default', () => {
43
+ const html = renderToString(<IssueRow issue={issue} open onToggle={() => {}} onResourceClick={() => {}} />)
44
+ expect(html).toContain('Subject')
45
+ })
46
+
47
+ it('suppresses the Subject deep-link when the issue subject is the embedding resource', () => {
48
+ // apiKind (plural) must still resolve to the same identity as the wire kind.
49
+ const html = renderToString(<IssueRow issue={issue} open onToggle={() => {}} onResourceClick={() => {}} hideSubject />)
50
+ expect(html).not.toContain('Subject')
51
+ })
52
+ })
53
+
54
+ describe('sameResource', () => {
55
+ const subject = { kind: 'PersistentVolumeClaim', namespace: 'demo', name: 'data' }
56
+
57
+ it('matches across singular kind vs plural API name', () => {
58
+ expect(sameResource(subject, { kind: 'persistentvolumeclaims', namespace: 'demo', name: 'data' })).toBe(true)
59
+ })
60
+
61
+ it('treats a missing namespace as empty', () => {
62
+ expect(sameResource({ kind: 'Node', name: 'n1' }, { kind: 'nodes', namespace: '', name: 'n1' })).toBe(true)
63
+ })
64
+
65
+ it('does not match a different name', () => {
66
+ expect(sameResource(subject, { kind: 'persistentvolumeclaims', namespace: 'demo', name: 'other' })).toBe(false)
67
+ })
68
+
69
+ it('does not match a different kind', () => {
70
+ expect(sameResource(subject, { kind: 'configmaps', namespace: 'demo', name: 'data' })).toBe(false)
71
+ })
40
72
  })
@@ -1,15 +1,22 @@
1
1
  import { useMemo, useState } from 'react'
2
2
  import { AlertTriangle } from 'lucide-react'
3
+ import { kindToPlural } from '../../utils/navigation'
3
4
  import { IssueRow } from './IssuesView'
4
- import { compareIssues, type Issue, type IssueResourceRef } from './types'
5
+ import { compareIssues, subjectRef, type Issue, type IssueResourceRef } from './types'
5
6
 
6
7
  export function ResourceIssuesSection({
7
8
  issues,
8
9
  onResourceClick,
10
+ subjectResource,
9
11
  }: {
10
12
  issues: Issue[] | undefined
11
13
  /** When provided, related resources in a causal link become clickable. */
12
14
  onResourceClick?: (ref: IssueResourceRef) => void
15
+ /** The resource this section is embedded under. When an issue's subject IS
16
+ * this resource, its redundant "Subject" deep-link is suppressed — the drawer
17
+ * header already names it. Omit (e.g. the standalone fleet queue) to always
18
+ * show the subject. */
19
+ subjectResource?: IssueResourceRef
13
20
  }) {
14
21
  const sorted = useMemo(() => [...(issues ?? [])].sort(compareIssues), [issues])
15
22
  const [openId, setOpenId] = useState<string | null>(null)
@@ -32,6 +39,7 @@ export function ResourceIssuesSection({
32
39
  open={openId === key}
33
40
  onToggle={() => setOpenId((cur) => (cur === key ? null : key))}
34
41
  onResourceClick={onResourceClick}
42
+ hideSubject={subjectResource ? sameResource(subjectRef(issue), subjectResource) : false}
35
43
  />
36
44
  )
37
45
  })}
@@ -40,6 +48,18 @@ export function ResourceIssuesSection({
40
48
  )
41
49
  }
42
50
 
51
+ // Identity match tolerant of singular/plural + casing on kind (the subject ref
52
+ // carries the wire kind, the host typically passes the plural API name) — group
53
+ // is deliberately ignored: a per-resource issue feed is already scoped to one
54
+ // resource, and kind+namespace+name uniquely identifies it there.
55
+ export function sameResource(a: IssueResourceRef, b: IssueResourceRef): boolean {
56
+ return (
57
+ a.name === b.name &&
58
+ (a.namespace ?? '') === (b.namespace ?? '') &&
59
+ kindToPlural(a.kind).toLowerCase() === kindToPlural(b.kind).toLowerCase()
60
+ )
61
+ }
62
+
43
63
  function issueKey(issue: Issue): string {
44
64
  return `${issue.cluster_id ?? ''}:${issue.id}`
45
65
  }
@@ -101,6 +101,7 @@ const CATEGORY_LABEL: Record<string, string> = {
101
101
  gitops_operation_failed: 'GitOps operation failed',
102
102
  gitops_out_of_sync: 'GitOps out of sync',
103
103
  gitops_health_degraded: 'GitOps health degraded',
104
+ gitops_stale: 'GitOps comparisons stale',
104
105
  helm_release_failed: 'Helm release failed',
105
106
  webhook_backend_down: 'Webhook backend down',
106
107
  control_plane_not_ready: 'Control plane not ready',
@@ -1,6 +1,6 @@
1
1
  import { Cpu, Network, Cloud } from 'lucide-react'
2
2
  import { clsx } from 'clsx'
3
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
4
4
  import { getCAPIConditions } from '../resource-utils-capi'
5
5
  import { getAWSMachineStatus, getAWSMachineInstanceType, getAWSMachineInstanceState, getAWSMachineInstanceID } from '../resource-utils-aws-capi'
6
6
 
@@ -16,6 +16,7 @@ export function AWSMachineRenderer({ data }: Props) {
16
16
 
17
17
  const machineStatus = getAWSMachineStatus(data)
18
18
  const isFailed = machineStatus.level === 'unhealthy'
19
+ const operationalIssuesShown = useOperationalIssuesShown()
19
20
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
21
 
21
22
  const instanceState = getAWSMachineInstanceState(data)
@@ -23,7 +24,7 @@ export function AWSMachineRenderer({ data }: Props) {
23
24
 
24
25
  return (
25
26
  <>
26
- {isFailed && (
27
+ {isFailed && !operationalIssuesShown && (
27
28
  <AlertBanner
28
29
  variant="error"
29
30
  title="AWS Machine Not Ready"
@@ -1,5 +1,5 @@
1
1
  import { Globe, Server } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { getCAPIConditions } from '../resource-utils-capi'
4
4
  import { getAWSManagedClusterStatus, getAWSManagedClusterEndpoint, getAWSManagedClusterFailureDomains } from '../resource-utils-aws-capi'
5
5
 
@@ -11,13 +11,14 @@ export function AWSManagedClusterRenderer({ data }: Props) {
11
11
  const conditions = getCAPIConditions(data)
12
12
  const clusterStatus = getAWSManagedClusterStatus(data)
13
13
  const isFailed = clusterStatus.level === 'unhealthy'
14
+ const operationalIssuesShown = useOperationalIssuesShown()
14
15
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
15
16
  const failureDomains = getAWSManagedClusterFailureDomains(data)
16
17
  const endpoint = getAWSManagedClusterEndpoint(data)
17
18
 
18
19
  return (
19
20
  <>
20
- {isFailed && (
21
+ {isFailed && !operationalIssuesShown && (
21
22
  <AlertBanner
22
23
  variant="error"
23
24
  title="Managed Cluster Not Ready"
@@ -1,5 +1,5 @@
1
1
  import { Globe, Network, Shield, Server, Package } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { getCAPIConditions } from '../resource-utils-capi'
4
4
  import {
5
5
  getAWSMCPStatus, getAWSMCPEKSClusterName, getAWSMCPRegion, getAWSMCPVersion,
@@ -18,6 +18,7 @@ export function AWSManagedControlPlaneRenderer({ data }: Props) {
18
18
 
19
19
  const mcpStatus = getAWSMCPStatus(data)
20
20
  const isFailed = mcpStatus.level === 'unhealthy'
21
+ const operationalIssuesShown = useOperationalIssuesShown()
21
22
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
22
23
 
23
24
  const vpc = getAWSMCPVPC(data)
@@ -29,7 +30,7 @@ export function AWSManagedControlPlaneRenderer({ data }: Props) {
29
30
 
30
31
  return (
31
32
  <>
32
- {isFailed && (
33
+ {isFailed && !operationalIssuesShown && (
33
34
  <AlertBanner
34
35
  variant="error"
35
36
  title="EKS Control Plane Not Ready"
@@ -1,6 +1,6 @@
1
1
  import { Server, Settings } from 'lucide-react'
2
2
  import { clsx } from 'clsx'
3
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
4
4
  import { getCAPIConditions } from '../resource-utils-capi'
5
5
  import { getAWSMMPStatus, getAWSMMPInstanceType, getAWSMMPCapacityType, getAWSMMPAMIType, getAWSMMPNodegroupName, getAWSMMPScaling } from '../resource-utils-aws-capi'
6
6
  import { CAPACITY_TYPE_BADGE } from '../../../utils/badge-colors'
@@ -17,6 +17,7 @@ export function AWSManagedMachinePoolRenderer({ data }: Props) {
17
17
 
18
18
  const mmpStatus = getAWSMMPStatus(data)
19
19
  const isFailed = mmpStatus.level === 'unhealthy'
20
+ const operationalIssuesShown = useOperationalIssuesShown()
20
21
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
21
22
 
22
23
  const scaling = getAWSMMPScaling(data)
@@ -26,7 +27,7 @@ export function AWSManagedMachinePoolRenderer({ data }: Props) {
26
27
 
27
28
  return (
28
29
  <>
29
- {isFailed && (
30
+ {isFailed && !operationalIssuesShown && (
30
31
  <AlertBanner
31
32
  variant="error"
32
33
  title="Managed Machine Pool Not Ready"
@@ -1,5 +1,5 @@
1
1
  import { Cloud } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { getCAPIConditions } from '../resource-utils-capi'
4
4
  import { getAzureMachineStatus, getAzureMachineVMSize, getAzureMachineProviderID } from '../resource-utils-azure-capi'
5
5
 
@@ -13,12 +13,13 @@ export function AzureMachineRenderer({ data }: Props) {
13
13
  const conditions = getCAPIConditions(data)
14
14
  const machineStatus = getAzureMachineStatus(data)
15
15
  const isFailed = machineStatus.level === 'unhealthy'
16
+ const operationalIssuesShown = useOperationalIssuesShown()
16
17
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
17
18
  const providerID = getAzureMachineProviderID(data)
18
19
 
19
20
  return (
20
21
  <>
21
- {isFailed && (
22
+ {isFailed && !operationalIssuesShown && (
22
23
  <AlertBanner variant="error" title="Azure Machine Not Ready" message={readyCond?.message || 'AzureMachine is not ready.'} />
23
24
  )}
24
25
 
@@ -1,5 +1,5 @@
1
1
  import { Globe, Shield, Settings } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { getCAPIConditions } from '../resource-utils-capi'
4
4
  import { getAzureMCPStatus, getAzureMCPLocation, getAzureMCPVersion, getAzureMCPResourceGroup, getAzureMCPSKUTier, getAzureMCPNetworkPlugin, getAzureMCPNetworkPolicy, getAzureMCPDNSPrefix, getAzureMCPUpgradeChannel, getAzureMCPPrivateCluster } from '../resource-utils-azure-capi'
5
5
 
@@ -13,13 +13,14 @@ export function AzureManagedControlPlaneRenderer({ data }: Props) {
13
13
  const conditions = getCAPIConditions(data)
14
14
  const mcpStatus = getAzureMCPStatus(data)
15
15
  const isFailed = mcpStatus.level === 'unhealthy'
16
+ const operationalIssuesShown = useOperationalIssuesShown()
16
17
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
17
18
  const apiAccess = spec.apiServerAccessProfile || {}
18
19
  const authorizedRanges = apiAccess.authorizedIPRanges || []
19
20
 
20
21
  return (
21
22
  <>
22
- {isFailed && (
23
+ {isFailed && !operationalIssuesShown && (
23
24
  <AlertBanner variant="error" title="AKS Control Plane Not Ready" message={readyCond?.message || 'AzureManagedControlPlane is not ready.'} />
24
25
  )}
25
26
 
@@ -1,6 +1,6 @@
1
1
  import { Server, Settings } from 'lucide-react'
2
2
  import { clsx } from 'clsx'
3
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
4
4
  import { getCAPIConditions } from '../resource-utils-capi'
5
5
  import { getAzureMMPStatus, getAzureMMPSKU, getAzureMMPMode, getAzureMMPOSDiskInfo, getAzureMMPScaling, getAzureMMPScaleSetPriority, getAzureMMPOSType } from '../resource-utils-azure-capi'
6
6
  import { CAPACITY_TYPE_BADGE, NODEPOOL_MODE_BADGE } from '../../../utils/badge-colors'
@@ -16,6 +16,7 @@ export function AzureManagedMachinePoolRenderer({ data }: Props) {
16
16
  const conditions = getCAPIConditions(data)
17
17
  const mmpStatus = getAzureMMPStatus(data)
18
18
  const isFailed = mmpStatus.level === 'unhealthy'
19
+ const operationalIssuesShown = useOperationalIssuesShown()
19
20
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
21
  const scaling = getAzureMMPScaling(data)
21
22
  const mode = getAzureMMPMode(data)
@@ -26,7 +27,7 @@ export function AzureManagedMachinePoolRenderer({ data }: Props) {
26
27
 
27
28
  return (
28
29
  <>
29
- {isFailed && (
30
+ {isFailed && !operationalIssuesShown && (
30
31
  <AlertBanner variant="error" title="AKS Node Pool Not Ready" message={readyCond?.message || 'AzureManagedMachinePool is not ready.'} />
31
32
  )}
32
33
 
@@ -1,6 +1,6 @@
1
1
  import { useState } from 'react'
2
2
  import { Server, Globe, Network, Layers, Download, CheckCircle, AlertCircle } from 'lucide-react'
3
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown} from '../../ui/drawer-components'
4
4
  import { kindToPlural } from '../../../utils/navigation'
5
5
  import { formatAge } from '../resource-utils'
6
6
  import { getClusterStatus, getClusterClass, getClusterVersion, getClusterEndpoint, getProviderFromInfraKind, parseCAPIConditionMessage } from '../resource-utils-capi'
@@ -18,6 +18,7 @@ export function CAPIClusterRenderer({ data, onNavigate, apiBase = '' }: Props) {
18
18
 
19
19
  const clusterStatus = getClusterStatus(data)
20
20
  const isFailed = clusterStatus.level === 'unhealthy'
21
+ const operationalIssuesShown = useOperationalIssuesShown()
21
22
  const readyCond = conditions.find((c: any) => c.type === 'Ready' || c.type === 'Available')
22
23
 
23
24
  const phase = status.phase || 'Unknown'
@@ -102,7 +103,7 @@ export function CAPIClusterRenderer({ data, onNavigate, apiBase = '' }: Props) {
102
103
  />
103
104
  )}
104
105
 
105
- {isFailed && (() => {
106
+ {isFailed && !operationalIssuesShown && (() => {
106
107
  const msg = readyCond?.message || `Cluster is in ${phase} state.`
107
108
  const items = parseCAPIConditionMessage(msg)
108
109
  return <AlertBanner variant="error" title="Cluster Not Ready" items={items || undefined} message={items ? undefined : msg} />
@@ -1,5 +1,5 @@
1
1
  import { Server, Shield, Settings } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { kindToPlural } from '../../../utils/navigation'
4
4
  import { formatAge } from '../resource-utils'
5
5
  import { getKCPStatus, getKCPVersion, getKCPInitialized, getMachineClusterName } from '../resource-utils-capi'
@@ -16,6 +16,7 @@ export function CAPIKubeadmControlPlaneRenderer({ data, onNavigate }: Props) {
16
16
 
17
17
  const kcpStatus = getKCPStatus(data)
18
18
  const isFailed = kcpStatus.level === 'unhealthy'
19
+ const operationalIssuesShown = useOperationalIssuesShown()
19
20
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
21
 
21
22
  const clusterName = getMachineClusterName(data)
@@ -33,7 +34,7 @@ export function CAPIKubeadmControlPlaneRenderer({ data, onNavigate }: Props) {
33
34
 
34
35
  return (
35
36
  <>
36
- {isFailed && (
37
+ {isFailed && !operationalIssuesShown && (
37
38
  <AlertBanner
38
39
  variant="error"
39
40
  title="Control Plane Not Ready"
@@ -1,5 +1,5 @@
1
1
  import { Server, Settings } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { kindToPlural } from '../../../utils/navigation'
4
4
  import { formatAge } from '../resource-utils'
5
5
  import { getMachineDeploymentStatus, getMachineDeploymentVersion, getMachineClusterName } from '../resource-utils-capi'
@@ -16,6 +16,7 @@ export function CAPIMachineDeploymentRenderer({ data, onNavigate }: Props) {
16
16
 
17
17
  const mdStatus = getMachineDeploymentStatus(data)
18
18
  const isFailed = mdStatus.level === 'unhealthy'
19
+ const operationalIssuesShown = useOperationalIssuesShown()
19
20
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
21
 
21
22
  const phase = status.phase || 'Unknown'
@@ -40,7 +41,7 @@ export function CAPIMachineDeploymentRenderer({ data, onNavigate }: Props) {
40
41
  />
41
42
  )}
42
43
 
43
- {isFailed && (
44
+ {isFailed && !operationalIssuesShown && (
44
45
  <AlertBanner
45
46
  variant="error"
46
47
  title="MachineDeployment Not Ready"
@@ -1,5 +1,5 @@
1
1
  import { HeartPulse, Settings, Shield } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, LabelSelectorDisplay } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, LabelSelectorDisplay, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { getMachineHealthCheckStatus, getMachineHealthCheckClusterName } from '../resource-utils-capi'
4
4
 
5
5
  interface Props {
@@ -14,6 +14,7 @@ export function CAPIMachineHealthCheckRenderer({ data }: Props) {
14
14
 
15
15
  const mhcStatus = getMachineHealthCheckStatus(data)
16
16
  const isFailed = mhcStatus.level === 'unhealthy'
17
+ const operationalIssuesShown = useOperationalIssuesShown()
17
18
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
18
19
 
19
20
  const clusterName = getMachineHealthCheckClusterName(data)
@@ -31,7 +32,7 @@ export function CAPIMachineHealthCheckRenderer({ data }: Props) {
31
32
 
32
33
  return (
33
34
  <>
34
- {isFailed && (
35
+ {isFailed && !operationalIssuesShown && (
35
36
  <AlertBanner
36
37
  variant="error"
37
38
  title="MachineHealthCheck Issue"
@@ -1,5 +1,5 @@
1
1
  import { Server, Settings } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { kindToPlural } from '../../../utils/navigation'
4
4
  import { formatAge } from '../resource-utils'
5
5
  import { getMachinePoolStatus } from '../resource-utils-capi'
@@ -16,6 +16,7 @@ export function CAPIMachinePoolRenderer({ data, onNavigate }: Props) {
16
16
 
17
17
  const mpStatus = getMachinePoolStatus(data)
18
18
  const isFailed = mpStatus.level === 'unhealthy'
19
+ const operationalIssuesShown = useOperationalIssuesShown()
19
20
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
21
 
21
22
  const clusterName = spec.clusterName || data.metadata?.labels?.['cluster.x-k8s.io/cluster-name'] || '-'
@@ -27,7 +28,7 @@ export function CAPIMachinePoolRenderer({ data, onNavigate }: Props) {
27
28
 
28
29
  return (
29
30
  <>
30
- {isFailed && (
31
+ {isFailed && !operationalIssuesShown && (
31
32
  <AlertBanner
32
33
  variant="error"
33
34
  title="MachinePool Not Ready"
@@ -1,5 +1,5 @@
1
1
  import { Cpu, Server, Network, Cloud } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { kindToPlural } from '../../../utils/navigation'
4
4
  import { formatAge } from '../resource-utils'
5
5
  import { getMachineStatus, getMachineRole, getMachineClusterName, getMachineNodeRef, getMachineVersion, getMachineProviderID, parseProviderID, getProviderFromInfraKind, parseCAPIConditionMessage } from '../resource-utils-capi'
@@ -16,6 +16,7 @@ export function CAPIMachineRenderer({ data, onNavigate }: Props) {
16
16
 
17
17
  const machineStatus = getMachineStatus(data)
18
18
  const isFailed = machineStatus.level === 'unhealthy'
19
+ const operationalIssuesShown = useOperationalIssuesShown()
19
20
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
21
  // Find the most informative False condition for the alert message
21
22
  const falseCond = conditions.find((c: any) =>
@@ -38,7 +39,7 @@ export function CAPIMachineRenderer({ data, onNavigate }: Props) {
38
39
 
39
40
  return (
40
41
  <>
41
- {isFailed && (() => {
42
+ {isFailed && !operationalIssuesShown && (() => {
42
43
  const msg = falseCond?.message || readyCond?.message || `Machine is in ${phase} state.`
43
44
  const items = parseCAPIConditionMessage(msg)
44
45
  return <AlertBanner variant="error" title="Machine Not Ready" items={items || undefined} message={items ? undefined : msg} />
@@ -1,5 +1,5 @@
1
1
  import { Server, Settings } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { kindToPlural } from '../../../utils/navigation'
4
4
  import { formatAge } from '../resource-utils'
5
5
  import { getMachineSetStatus, getMachineClusterName } from '../resource-utils-capi'
@@ -16,6 +16,7 @@ export function CAPIMachineSetRenderer({ data, onNavigate }: Props) {
16
16
 
17
17
  const msStatus = getMachineSetStatus(data)
18
18
  const isFailed = msStatus.level === 'unhealthy'
19
+ const operationalIssuesShown = useOperationalIssuesShown()
19
20
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
21
 
21
22
  const clusterName = getMachineClusterName(data)
@@ -29,7 +30,7 @@ export function CAPIMachineSetRenderer({ data, onNavigate }: Props) {
29
30
 
30
31
  return (
31
32
  <>
32
- {isFailed && (
33
+ {isFailed && !operationalIssuesShown && (
33
34
  <AlertBanner
34
35
  variant="error"
35
36
  title="MachineSet Not Ready"
@@ -1,6 +1,6 @@
1
1
  import { Shield, Clock, Globe, Key } from 'lucide-react'
2
2
  import { clsx } from 'clsx'
3
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
4
4
  import { pluralize } from '../../../utils/pluralize'
5
5
 
6
6
  interface CertificateRendererProps {
@@ -34,6 +34,7 @@ export function CertificateRenderer({ data }: CertificateRendererProps) {
34
34
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
35
35
  const isReady = readyCond?.status === 'True'
36
36
  const isNotReady = readyCond?.status === 'False'
37
+ const operationalIssuesShown = useOperationalIssuesShown()
37
38
 
38
39
  const notAfter = status.notAfter
39
40
  const notBefore = status.notBefore
@@ -69,7 +70,7 @@ export function CertificateRenderer({ data }: CertificateRendererProps) {
69
70
  return (
70
71
  <>
71
72
  {/* Problem detection alerts */}
72
- {isNotReady && (
73
+ {isNotReady && !operationalIssuesShown && (
73
74
  <AlertBanner
74
75
  variant="error"
75
76
  title="Certificate Not Ready"
@@ -1,6 +1,6 @@
1
1
  import { Shield, FileText, Info } from 'lucide-react'
2
2
  import { clsx } from 'clsx'
3
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
4
4
 
5
5
  interface CertificateRequestRendererProps {
6
6
  data: any
@@ -20,6 +20,7 @@ export function CertificateRequestRenderer({ data }: CertificateRequestRendererP
20
20
 
21
21
  const isReady = readyCond?.status === 'True'
22
22
  const isNotReady = readyCond?.status === 'False'
23
+ const operationalIssuesShown = useOperationalIssuesShown()
23
24
  const isApproved = approvedCond?.status === 'True'
24
25
  const isDenied = deniedCond?.status === 'True'
25
26
 
@@ -28,7 +29,7 @@ export function CertificateRequestRenderer({ data }: CertificateRequestRendererP
28
29
  return (
29
30
  <>
30
31
  {/* Problem detection alerts */}
31
- {isNotReady && (
32
+ {isNotReady && !operationalIssuesShown && (
32
33
  <AlertBanner
33
34
  variant="error"
34
35
  title="Certificate Request Not Ready"
@@ -1,6 +1,6 @@
1
1
  import { clsx } from 'clsx'
2
2
  import { Layers, GitBranch, Box, ChevronRight, Pause } from 'lucide-react'
3
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown} from '../../ui/drawer-components'
4
4
  import { Tooltip } from '../../ui/Tooltip'
5
5
  import {
6
6
  getCrossplaneStatus,
@@ -78,6 +78,7 @@ export function CompositeRenderer({ data, onNavigate, composedRefStatuses }: Com
78
78
  const boundXR = claim ? getBoundXRRef(data) : null
79
79
  const paused = isCrossplanePaused(data)
80
80
  const alertVariant = status.level === 'unhealthy' || status.level === 'alert' ? status.level : null
81
+ const operationalIssuesShown = useOperationalIssuesShown()
81
82
 
82
83
  return (
83
84
  <>
@@ -90,7 +91,7 @@ export function CompositeRenderer({ data, onNavigate, composedRefStatuses }: Com
90
91
  />
91
92
  )}
92
93
 
93
- {alertVariant && reason && (
94
+ {alertVariant && reason && !operationalIssuesShown && (
94
95
  <AlertBanner
95
96
  variant={alertVariant === 'unhealthy' ? 'error' : 'warning'}
96
97
  title={status.text}
@@ -1,5 +1,5 @@
1
1
  import { KeyRound, RefreshCw, CloudCog } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink, useOperationalIssuesShown } from '../../ui/drawer-components'
3
3
  import {
4
4
  getExternalSecretStatus,
5
5
  getExternalSecretStore,
@@ -30,11 +30,12 @@ export function ExternalSecretRenderer({ data, onNavigate }: ExternalSecretRende
30
30
  // Problem detection
31
31
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
32
32
  const isNotReady = readyCond?.status === 'False'
33
+ const operationalIssuesShown = useOperationalIssuesShown()
33
34
 
34
35
  return (
35
36
  <>
36
37
  {/* Alert if not synced */}
37
- {isNotReady && (
38
+ {isNotReady && !operationalIssuesShown && (
38
39
  <AlertBanner
39
40
  variant="error"
40
41
  title="ExternalSecret Not Synced"
@@ -1,5 +1,5 @@
1
1
  import { Cpu, Cloud } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { getCAPIConditions } from '../resource-utils-capi'
4
4
  import { getGCPMachineStatus, getGCPMachineInstanceType, getGCPMachineZone, getGCPMachineInstanceID } from '../resource-utils-gcp-capi'
5
5
 
@@ -13,11 +13,12 @@ export function GCPMachineRenderer({ data }: Props) {
13
13
  const conditions = getCAPIConditions(data)
14
14
  const machineStatus = getGCPMachineStatus(data)
15
15
  const isFailed = machineStatus.level === 'unhealthy'
16
+ const operationalIssuesShown = useOperationalIssuesShown()
16
17
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
17
18
 
18
19
  return (
19
20
  <>
20
- {isFailed && (
21
+ {isFailed && !operationalIssuesShown && (
21
22
  <AlertBanner variant="error" title="GCP Machine Not Ready" message={readyCond?.message || 'GCPMachine is not ready.'} />
22
23
  )}
23
24
 
@@ -1,5 +1,5 @@
1
1
  import { Globe, Shield, Settings } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { getCAPIConditions } from '../resource-utils-capi'
4
4
  import { getGCPMCPStatus, getGCPMCPClusterName, getGCPMCPProject, getGCPMCPLocation, getGCPMCPVersion, getGCPMCPReleaseChannel, getGCPMCPAutopilot, getGCPMCPEndpoint } from '../resource-utils-gcp-capi'
5
5
 
@@ -13,13 +13,14 @@ export function GCPManagedControlPlaneRenderer({ data }: Props) {
13
13
  const conditions = getCAPIConditions(data)
14
14
  const mcpStatus = getGCPMCPStatus(data)
15
15
  const isFailed = mcpStatus.level === 'unhealthy'
16
+ const operationalIssuesShown = useOperationalIssuesShown()
16
17
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
17
18
  const endpoint = getGCPMCPEndpoint(data)
18
19
  const masterAuth = spec.master_authorized_networks_config
19
20
 
20
21
  return (
21
22
  <>
22
- {isFailed && (
23
+ {isFailed && !operationalIssuesShown && (
23
24
  <AlertBanner variant="error" title="GKE Control Plane Not Ready" message={readyCond?.message || 'GCPManagedControlPlane is not ready.'} />
24
25
  )}
25
26
 
@@ -1,5 +1,5 @@
1
1
  import { Server, Settings } from 'lucide-react'
2
- import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, useOperationalIssuesShown} from '../../ui/drawer-components'
3
3
  import { getCAPIConditions } from '../resource-utils-capi'
4
4
  import { getGCPMMPStatus, getGCPMMPNodePoolName, getGCPMMPMachineType, getGCPMMPDiskInfo, getGCPMMPScaling, getGCPMMPImageType } from '../resource-utils-gcp-capi'
5
5
 
@@ -14,6 +14,7 @@ export function GCPManagedMachinePoolRenderer({ data }: Props) {
14
14
  const conditions = getCAPIConditions(data)
15
15
  const mmpStatus = getGCPMMPStatus(data)
16
16
  const isFailed = mmpStatus.level === 'unhealthy'
17
+ const operationalIssuesShown = useOperationalIssuesShown()
17
18
  const readyCond = conditions.find((c: any) => c.type === 'Ready')
18
19
  const scaling = getGCPMMPScaling(data)
19
20
  const management = spec.management || {}
@@ -23,7 +24,7 @@ export function GCPManagedMachinePoolRenderer({ data }: Props) {
23
24
 
24
25
  return (
25
26
  <>
26
- {isFailed && (
27
+ {isFailed && !operationalIssuesShown && (
27
28
  <AlertBanner variant="error" title="GKE Node Pool Not Ready" message={readyCond?.message || 'GCPManagedMachinePool is not ready.'} />
28
29
  )}
29
30