@skyhook-io/k8s-ui 1.4.2 → 1.4.3

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 (44) hide show
  1. package/package.json +1 -1
  2. package/src/components/resources/ResourcesView.tsx +267 -14
  3. package/src/components/resources/renderers/AWSMachineRenderer.tsx +96 -0
  4. package/src/components/resources/renderers/AWSMachineTemplateRenderer.tsx +39 -0
  5. package/src/components/resources/renderers/AWSManagedClusterRenderer.tsx +49 -0
  6. package/src/components/resources/renderers/AWSManagedControlPlaneRenderer.tsx +174 -0
  7. package/src/components/resources/renderers/AWSManagedMachinePoolRenderer.tsx +89 -0
  8. package/src/components/resources/renderers/AzureMachineRenderer.tsx +41 -0
  9. package/src/components/resources/renderers/AzureManagedControlPlaneRenderer.tsx +71 -0
  10. package/src/components/resources/renderers/AzureManagedMachinePoolRenderer.tsx +106 -0
  11. package/src/components/resources/renderers/CAPIClusterClassRenderer.tsx +156 -0
  12. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +240 -0
  13. package/src/components/resources/renderers/CAPIKubeadmConfigRenderer.tsx +97 -0
  14. package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +124 -0
  15. package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +128 -0
  16. package/src/components/resources/renderers/CAPIMachineDrainRuleRenderer.tsx +38 -0
  17. package/src/components/resources/renderers/CAPIMachineHealthCheckRenderer.tsx +146 -0
  18. package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +92 -0
  19. package/src/components/resources/renderers/CAPIMachineRenderer.tsx +170 -0
  20. package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +101 -0
  21. package/src/components/resources/renderers/GCPMachineRenderer.tsx +53 -0
  22. package/src/components/resources/renderers/GCPManagedControlPlaneRenderer.tsx +75 -0
  23. package/src/components/resources/renderers/GCPManagedMachinePoolRenderer.tsx +102 -0
  24. package/src/components/resources/renderers/aws-capi-cells.tsx +117 -0
  25. package/src/components/resources/renderers/azure-capi-cells.tsx +83 -0
  26. package/src/components/resources/renderers/capi-cells.tsx +158 -0
  27. package/src/components/resources/renderers/gcp-capi-cells.tsx +68 -0
  28. package/src/components/resources/renderers/index.ts +25 -0
  29. package/src/components/resources/resource-utils-aws-capi.ts +211 -0
  30. package/src/components/resources/resource-utils-azure-capi.ts +123 -0
  31. package/src/components/resources/resource-utils-capi.ts +305 -0
  32. package/src/components/resources/resource-utils-gcp-capi.ts +130 -0
  33. package/src/components/shared/ResourceRendererDispatch.tsx +97 -2
  34. package/src/components/topology/K8sResourceNode.tsx +8 -0
  35. package/src/components/topology/TopologyControls.tsx +16 -0
  36. package/src/components/topology/TopologyFilterSidebar.tsx +15 -0
  37. package/src/components/topology/TopologyGraph.tsx +2 -1
  38. package/src/components/topology/layout.ts +11 -0
  39. package/src/components/topology/topology.css +29 -0
  40. package/src/components/ui/Badge.tsx +10 -0
  41. package/src/components/ui/drawer-components.tsx +54 -17
  42. package/src/types/core.ts +18 -2
  43. package/src/utils/api-resources.ts +4 -0
  44. package/src/utils/resource-icons.ts +35 -0
@@ -0,0 +1,146 @@
1
+ import { HeartPulse, Settings, Shield } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, LabelSelectorDisplay } from '../../ui/drawer-components'
3
+ import { getMachineHealthCheckStatus, getMachineHealthCheckClusterName } from '../resource-utils-capi'
4
+
5
+ interface Props {
6
+ data: any
7
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
8
+ }
9
+
10
+ export function CAPIMachineHealthCheckRenderer({ data }: Props) {
11
+ const status = data.status || {}
12
+ const spec = data.spec || {}
13
+ const conditions = status.v1beta2?.conditions || status.conditions || []
14
+
15
+ const mhcStatus = getMachineHealthCheckStatus(data)
16
+ const isFailed = mhcStatus.level === 'unhealthy'
17
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
18
+
19
+ const clusterName = getMachineHealthCheckClusterName(data)
20
+ const expectedMachines = status.expectedMachines ?? 0
21
+ const currentHealthy = status.currentHealthy ?? 0
22
+ const remediationsAllowed = status.remediationsAllowed ?? 0
23
+ const unhealthyConditions = spec.unhealthyConditions || []
24
+ const nodeStartupTimeout = spec.nodeStartupTimeout
25
+ const selector = spec.selector || {}
26
+ const remediationTemplate = spec.remediationTemplate || {}
27
+
28
+ // v1beta2 fields
29
+ const unhealthyNodeConditions = spec.unhealthyNodeConditions || []
30
+ const unhealthyMachineConditions = spec.unhealthyMachineConditions || []
31
+
32
+ return (
33
+ <>
34
+ {isFailed && (
35
+ <AlertBanner
36
+ variant="error"
37
+ title="MachineHealthCheck Issue"
38
+ message={readyCond?.message || 'MachineHealthCheck has unhealthy machines.'}
39
+ />
40
+ )}
41
+
42
+ <Section title="Overview" icon={HeartPulse}>
43
+ <PropertyList>
44
+ <Property label="Cluster" value={clusterName} />
45
+ <Property label="Expected Machines" value={String(expectedMachines)} />
46
+ <Property label="Current Healthy" value={`${currentHealthy}/${expectedMachines}`} />
47
+ <Property label="Remediations Allowed" value={String(remediationsAllowed)} />
48
+ {nodeStartupTimeout && <Property label="Node Startup Timeout" value={nodeStartupTimeout} />}
49
+ {spec.maxUnhealthy != null && <Property label="Max Unhealthy" value={String(spec.maxUnhealthy)} />}
50
+ {spec.unhealthyRange != null && <Property label="Unhealthy Range" value={spec.unhealthyRange} />}
51
+ </PropertyList>
52
+ </Section>
53
+
54
+ {/* Selector */}
55
+ {(selector.matchLabels || selector.matchExpressions) && (
56
+ <Section title="Selector" icon={Settings}>
57
+ <LabelSelectorDisplay selector={selector} />
58
+ </Section>
59
+ )}
60
+
61
+ {/* Unhealthy Conditions (v1beta1) */}
62
+ {unhealthyConditions.length > 0 && (
63
+ <Section title="Unhealthy Conditions" icon={Shield}>
64
+ <table className="w-full text-xs">
65
+ <thead>
66
+ <tr className="text-theme-text-tertiary">
67
+ <th className="text-left font-medium py-1">Type</th>
68
+ <th className="text-left font-medium py-1">Status</th>
69
+ <th className="text-left font-medium py-1">Timeout</th>
70
+ </tr>
71
+ </thead>
72
+ <tbody>
73
+ {unhealthyConditions.map((uc: any, i: number) => (
74
+ <tr key={i} className="border-t border-theme-border">
75
+ <td className="py-1 text-theme-text-secondary">{uc.type}</td>
76
+ <td className="py-1 text-theme-text-secondary">{uc.status}</td>
77
+ <td className="py-1 text-theme-text-secondary">{uc.timeout}</td>
78
+ </tr>
79
+ ))}
80
+ </tbody>
81
+ </table>
82
+ </Section>
83
+ )}
84
+
85
+ {/* v1beta2: unhealthy node/machine conditions */}
86
+ {unhealthyNodeConditions.length > 0 && (
87
+ <Section title="Unhealthy Node Conditions" icon={Shield}>
88
+ <table className="w-full text-xs">
89
+ <thead>
90
+ <tr className="text-theme-text-tertiary">
91
+ <th className="text-left font-medium py-1">Type</th>
92
+ <th className="text-left font-medium py-1">Status</th>
93
+ <th className="text-left font-medium py-1">Timeout</th>
94
+ </tr>
95
+ </thead>
96
+ <tbody>
97
+ {unhealthyNodeConditions.map((uc: any, i: number) => (
98
+ <tr key={i} className="border-t border-theme-border">
99
+ <td className="py-1 text-theme-text-secondary">{uc.type}</td>
100
+ <td className="py-1 text-theme-text-secondary">{uc.status}</td>
101
+ <td className="py-1 text-theme-text-secondary">{uc.timeout}</td>
102
+ </tr>
103
+ ))}
104
+ </tbody>
105
+ </table>
106
+ </Section>
107
+ )}
108
+
109
+ {unhealthyMachineConditions.length > 0 && (
110
+ <Section title="Unhealthy Machine Conditions" icon={Shield}>
111
+ <table className="w-full text-xs">
112
+ <thead>
113
+ <tr className="text-theme-text-tertiary">
114
+ <th className="text-left font-medium py-1">Type</th>
115
+ <th className="text-left font-medium py-1">Status</th>
116
+ <th className="text-left font-medium py-1">Timeout</th>
117
+ </tr>
118
+ </thead>
119
+ <tbody>
120
+ {unhealthyMachineConditions.map((uc: any, i: number) => (
121
+ <tr key={i} className="border-t border-theme-border">
122
+ <td className="py-1 text-theme-text-secondary">{uc.type}</td>
123
+ <td className="py-1 text-theme-text-secondary">{uc.status}</td>
124
+ <td className="py-1 text-theme-text-secondary">{uc.timeout}</td>
125
+ </tr>
126
+ ))}
127
+ </tbody>
128
+ </table>
129
+ </Section>
130
+ )}
131
+
132
+ {/* Remediation Template */}
133
+ {remediationTemplate.kind && (
134
+ <Section title="Remediation Template" icon={Settings}>
135
+ <PropertyList>
136
+ <Property label="Kind" value={remediationTemplate.kind} />
137
+ <Property label="Name" value={remediationTemplate.name || '-'} />
138
+ {remediationTemplate.namespace && <Property label="Namespace" value={remediationTemplate.namespace} />}
139
+ </PropertyList>
140
+ </Section>
141
+ )}
142
+
143
+ <ConditionsSection conditions={conditions} />
144
+ </>
145
+ )
146
+ }
@@ -0,0 +1,92 @@
1
+ import { Server, Settings } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
+ import { kindToPlural } from '../../../utils/navigation'
4
+ import { formatAge } from '../resource-utils'
5
+ import { getMachinePoolStatus } from '../resource-utils-capi'
6
+
7
+ interface Props {
8
+ data: any
9
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
10
+ }
11
+
12
+ export function CAPIMachinePoolRenderer({ data, onNavigate }: Props) {
13
+ const status = data.status || {}
14
+ const spec = data.spec || {}
15
+ const conditions = status.v1beta2?.conditions || status.conditions || []
16
+
17
+ const mpStatus = getMachinePoolStatus(data)
18
+ const isFailed = mpStatus.level === 'unhealthy'
19
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
+
21
+ const clusterName = spec.clusterName || data.metadata?.labels?.['cluster.x-k8s.io/cluster-name'] || '-'
22
+ const phase = status.phase || 'Unknown'
23
+ const desired = spec.replicas ?? 0
24
+ const ready = status.readyReplicas ?? 0
25
+ const infraRef = spec.template?.spec?.infrastructureRef || {}
26
+ const bootstrapRef = spec.template?.spec?.bootstrap?.configRef || {}
27
+
28
+ return (
29
+ <>
30
+ {isFailed && (
31
+ <AlertBanner
32
+ variant="error"
33
+ title="MachinePool Not Ready"
34
+ message={readyCond?.message || `MachinePool is in ${phase} state.`}
35
+ />
36
+ )}
37
+
38
+ <Section title="Overview" icon={Server}>
39
+ <PropertyList>
40
+ <Property label="Phase" value={phase} />
41
+ <Property label="Cluster" value={clusterName} />
42
+ {spec.minReadySeconds != null && (
43
+ <Property label="Min Ready Seconds" value={String(spec.minReadySeconds)} />
44
+ )}
45
+ {readyCond?.lastTransitionTime && (
46
+ <Property label="Since" value={formatAge(readyCond.lastTransitionTime)} />
47
+ )}
48
+ </PropertyList>
49
+ </Section>
50
+
51
+ <Section title="Replicas" icon={Server}>
52
+ <PropertyList>
53
+ <Property label="Desired" value={String(desired)} />
54
+ <Property label="Ready" value={String(ready)} />
55
+ </PropertyList>
56
+ </Section>
57
+
58
+ {(infraRef.kind || bootstrapRef.kind) && (
59
+ <Section title="Machine Template" icon={Settings}>
60
+ <PropertyList>
61
+ {infraRef.kind && (
62
+ <Property label="Infrastructure" value={
63
+ <ResourceLink
64
+ name={infraRef.name}
65
+ kind={kindToPlural(infraRef.kind)}
66
+ namespace={infraRef.namespace || data.metadata?.namespace}
67
+ group={infraRef.apiVersion?.split('/')?.[0]}
68
+ label={`${infraRef.kind}/${infraRef.name}`}
69
+ onNavigate={onNavigate}
70
+ />
71
+ } />
72
+ )}
73
+ {bootstrapRef.kind && (
74
+ <Property label="Bootstrap" value={
75
+ <ResourceLink
76
+ name={bootstrapRef.name}
77
+ kind={kindToPlural(bootstrapRef.kind)}
78
+ namespace={bootstrapRef.namespace || data.metadata?.namespace}
79
+ group={bootstrapRef.apiVersion?.split('/')?.[0]}
80
+ label={`${bootstrapRef.kind}/${bootstrapRef.name}`}
81
+ onNavigate={onNavigate}
82
+ />
83
+ } />
84
+ )}
85
+ </PropertyList>
86
+ </Section>
87
+ )}
88
+
89
+ <ConditionsSection conditions={conditions} />
90
+ </>
91
+ )
92
+ }
@@ -0,0 +1,170 @@
1
+ import { Cpu, Server, Network, Cloud } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
+ import { kindToPlural } from '../../../utils/navigation'
4
+ import { formatAge } from '../resource-utils'
5
+ import { getMachineStatus, getMachineRole, getMachineClusterName, getMachineNodeRef, getMachineVersion, getMachineProviderID, parseProviderID, getProviderFromInfraKind, parseCAPIConditionMessage } from '../resource-utils-capi'
6
+
7
+ interface Props {
8
+ data: any
9
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
10
+ }
11
+
12
+ export function CAPIMachineRenderer({ data, onNavigate }: Props) {
13
+ const status = data.status || {}
14
+ const spec = data.spec || {}
15
+ const conditions = status.v1beta2?.conditions || status.conditions || []
16
+
17
+ const machineStatus = getMachineStatus(data)
18
+ const isFailed = machineStatus.level === 'unhealthy'
19
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
+ // Find the most informative False condition for the alert message
21
+ const falseCond = conditions.find((c: any) =>
22
+ c.status === 'False' && ['BootstrapReady', 'InfrastructureReady', 'NodeHealthy', 'Ready'].includes(c.type)
23
+ )
24
+
25
+ const phase = status.phase || 'Unknown'
26
+ const role = getMachineRole(data)
27
+ const clusterName = getMachineClusterName(data)
28
+ const nodeName = getMachineNodeRef(data)
29
+ const version = getMachineVersion(data)
30
+ const providerID = getMachineProviderID(data)
31
+ const addresses = status.addresses || []
32
+ const nodeInfo = status.nodeInfo || {}
33
+ const nodeRef = status.nodeRef || {}
34
+ const bootstrapRef = spec.bootstrap?.configRef || {}
35
+ const infraRef = spec.infrastructureRef || {}
36
+ const parsedProvider = parseProviderID(providerID)
37
+ const infraProvider = infraRef.kind ? getProviderFromInfraKind(infraRef.kind) : parsedProvider?.provider
38
+
39
+ return (
40
+ <>
41
+ {isFailed && (() => {
42
+ const msg = falseCond?.message || readyCond?.message || `Machine is in ${phase} state.`
43
+ const items = parseCAPIConditionMessage(msg)
44
+ return <AlertBanner variant="error" title="Machine Not Ready" items={items || undefined} message={items ? undefined : msg} />
45
+ })()}
46
+ {!isFailed && falseCond && (() => {
47
+ const items = parseCAPIConditionMessage(falseCond.message || '')
48
+ return <AlertBanner variant="warning" title={`${falseCond.type}: ${falseCond.reason || 'False'}`} items={items || undefined} message={items ? undefined : (falseCond.message || `Condition ${falseCond.type} is False.`)} />
49
+ })()}
50
+
51
+ {/* Overview */}
52
+ <Section title="Overview" icon={Cpu}>
53
+ <PropertyList>
54
+ <Property label="Phase" value={phase} />
55
+ <Property label="Role" value={role} />
56
+ <Property label="Cluster" value={clusterName} />
57
+ <Property label="Version" value={version} />
58
+ {infraProvider && <Property label="Provider" value={infraProvider} />}
59
+ {spec.failureDomain && <Property label="Failure Domain" value={spec.failureDomain} />}
60
+ {readyCond?.lastTransitionTime && (
61
+ <Property label="Since" value={formatAge(readyCond.lastTransitionTime)} />
62
+ )}
63
+ </PropertyList>
64
+ </Section>
65
+
66
+ {/* Infrastructure (parsed from providerID) */}
67
+ {parsedProvider && (
68
+ <Section title="Infrastructure" icon={Cloud}>
69
+ <PropertyList>
70
+ {parsedProvider.region && <Property label="Zone / Region" value={parsedProvider.region} />}
71
+ {parsedProvider.instanceId && <Property label="Instance ID" value={parsedProvider.instanceId} />}
72
+ {providerID !== '-' && <Property label="Provider ID" value={
73
+ <span className="font-mono text-[10px] break-all">{providerID}</span>
74
+ } />}
75
+ </PropertyList>
76
+ </Section>
77
+ )}
78
+
79
+ {/* Node Reference */}
80
+ {nodeName !== '-' && (
81
+ <Section title="Node" icon={Server}>
82
+ <PropertyList>
83
+ <Property
84
+ label="Name"
85
+ value={
86
+ <ResourceLink
87
+ name={nodeName}
88
+ kind="nodes"
89
+ namespace=""
90
+ label={nodeName}
91
+ onNavigate={onNavigate}
92
+ />
93
+ }
94
+ />
95
+ {nodeRef.uid && <Property label="UID" value={nodeRef.uid} />}
96
+ </PropertyList>
97
+ </Section>
98
+ )}
99
+
100
+ {/* References */}
101
+ {(bootstrapRef.kind || infraRef.kind) && (
102
+ <Section title="References" icon={Network}>
103
+ <PropertyList>
104
+ {bootstrapRef.kind && (
105
+ <Property label="Bootstrap" value={
106
+ <ResourceLink
107
+ name={bootstrapRef.name}
108
+ kind={kindToPlural(bootstrapRef.kind)}
109
+ namespace={bootstrapRef.namespace || data.metadata?.namespace}
110
+ group={bootstrapRef.apiVersion?.split('/')?.[0]}
111
+ label={`${bootstrapRef.kind}/${bootstrapRef.name}`}
112
+ onNavigate={onNavigate}
113
+ />
114
+ } />
115
+ )}
116
+ {infraRef.kind && (
117
+ <Property label="Infrastructure" value={
118
+ <ResourceLink
119
+ name={infraRef.name}
120
+ kind={kindToPlural(infraRef.kind)}
121
+ namespace={infraRef.namespace || data.metadata?.namespace}
122
+ group={infraRef.apiVersion?.split('/')?.[0]}
123
+ label={`${infraRef.kind}/${infraRef.name}`}
124
+ onNavigate={onNavigate}
125
+ />
126
+ } />
127
+ )}
128
+ </PropertyList>
129
+ </Section>
130
+ )}
131
+
132
+ {/* Addresses */}
133
+ {addresses.length > 0 && (
134
+ <Section title="Addresses" icon={Network}>
135
+ <table className="w-full text-xs">
136
+ <thead>
137
+ <tr className="text-theme-text-tertiary">
138
+ <th className="text-left font-medium py-1">Type</th>
139
+ <th className="text-left font-medium py-1">Address</th>
140
+ </tr>
141
+ </thead>
142
+ <tbody>
143
+ {addresses.map((addr: any, i: number) => (
144
+ <tr key={i} className="border-t border-theme-border">
145
+ <td className="py-1 text-theme-text-secondary">{addr.type}</td>
146
+ <td className="py-1 text-theme-text-secondary font-mono">{addr.address}</td>
147
+ </tr>
148
+ ))}
149
+ </tbody>
150
+ </table>
151
+ </Section>
152
+ )}
153
+
154
+ {/* Node Info */}
155
+ {nodeInfo.kubeletVersion && (
156
+ <Section title="Node Info" icon={Server}>
157
+ <PropertyList>
158
+ {nodeInfo.osImage && <Property label="OS Image" value={nodeInfo.osImage} />}
159
+ {nodeInfo.architecture && <Property label="Architecture" value={nodeInfo.architecture} />}
160
+ {nodeInfo.kernelVersion && <Property label="Kernel" value={nodeInfo.kernelVersion} />}
161
+ {nodeInfo.containerRuntimeVersion && <Property label="Container Runtime" value={nodeInfo.containerRuntimeVersion} />}
162
+ {nodeInfo.kubeletVersion && <Property label="Kubelet" value={nodeInfo.kubeletVersion} />}
163
+ </PropertyList>
164
+ </Section>
165
+ )}
166
+
167
+ <ConditionsSection conditions={conditions} />
168
+ </>
169
+ )
170
+ }
@@ -0,0 +1,101 @@
1
+ import { Server, Settings } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
+ import { kindToPlural } from '../../../utils/navigation'
4
+ import { formatAge } from '../resource-utils'
5
+ import { getMachineSetStatus, getMachineClusterName } from '../resource-utils-capi'
6
+
7
+ interface Props {
8
+ data: any
9
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
10
+ }
11
+
12
+ export function CAPIMachineSetRenderer({ data, onNavigate }: Props) {
13
+ const status = data.status || {}
14
+ const spec = data.spec || {}
15
+ const conditions = status.v1beta2?.conditions || status.conditions || []
16
+
17
+ const msStatus = getMachineSetStatus(data)
18
+ const isFailed = msStatus.level === 'unhealthy'
19
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
20
+
21
+ const clusterName = getMachineClusterName(data)
22
+ const desired = spec.replicas ?? 0
23
+ const ready = status.readyReplicas ?? 0
24
+ const available = status.availableReplicas ?? 0
25
+ const upToDate = status.upToDateReplicas ?? status.updatedReplicas ?? 0
26
+ const deletePolicy = spec.deletePolicy || 'Random'
27
+ const infraRef = spec.template?.spec?.infrastructureRef || {}
28
+ const bootstrapRef = spec.template?.spec?.bootstrap?.configRef || {}
29
+
30
+ return (
31
+ <>
32
+ {isFailed && (
33
+ <AlertBanner
34
+ variant="error"
35
+ title="MachineSet Not Ready"
36
+ message={readyCond?.message || 'MachineSet is not ready.'}
37
+ />
38
+ )}
39
+
40
+ <Section title="Overview" icon={Server}>
41
+ <PropertyList>
42
+ <Property label="Cluster" value={clusterName} />
43
+ <Property label="Delete Policy" value={deletePolicy} />
44
+ {spec.minReadySeconds != null && (
45
+ <Property label="Min Ready Seconds" value={String(spec.minReadySeconds)} />
46
+ )}
47
+ {readyCond?.lastTransitionTime && (
48
+ <Property label="Since" value={formatAge(readyCond.lastTransitionTime)} />
49
+ )}
50
+ </PropertyList>
51
+ </Section>
52
+
53
+ <Section title="Replicas" icon={Server}>
54
+ <PropertyList>
55
+ <Property label="Desired" value={String(desired)} />
56
+ <Property label="Ready" value={String(ready)} />
57
+ <Property label="Available" value={String(available)} />
58
+ <Property label="Up-to-date" value={String(upToDate)} />
59
+ </PropertyList>
60
+ </Section>
61
+
62
+ {(infraRef.kind || bootstrapRef.kind) && (
63
+ <Section title="Machine Template" icon={Settings}>
64
+ <PropertyList>
65
+ {infraRef.kind && (
66
+ <Property label="Infrastructure" value={
67
+ <ResourceLink
68
+ name={infraRef.name}
69
+ kind={kindToPlural(infraRef.kind)}
70
+ namespace={infraRef.namespace || data.metadata?.namespace}
71
+ group={infraRef.apiVersion?.split('/')?.[0]}
72
+ label={`${infraRef.kind}/${infraRef.name}`}
73
+ onNavigate={onNavigate}
74
+ />
75
+ } />
76
+ )}
77
+ {bootstrapRef.kind && (
78
+ <Property label="Bootstrap" value={
79
+ <ResourceLink
80
+ name={bootstrapRef.name}
81
+ kind={kindToPlural(bootstrapRef.kind)}
82
+ namespace={bootstrapRef.namespace || data.metadata?.namespace}
83
+ group={bootstrapRef.apiVersion?.split('/')?.[0]}
84
+ label={`${bootstrapRef.kind}/${bootstrapRef.name}`}
85
+ onNavigate={onNavigate}
86
+ />
87
+ } />
88
+ )}
89
+ </PropertyList>
90
+ </Section>
91
+ )}
92
+
93
+ {/* Owned Machines hint */}
94
+ <div className="px-3 py-1.5 text-xs text-theme-text-tertiary">
95
+ Machines with label <code className="bg-theme-surface px-1 py-0.5 rounded text-[10px] font-mono select-all">cluster.x-k8s.io/set-name={data.metadata?.name}</code>
96
+ </div>
97
+
98
+ <ConditionsSection conditions={conditions} />
99
+ </>
100
+ )
101
+ }
@@ -0,0 +1,53 @@
1
+ import { Cpu, Cloud } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import { getCAPIConditions } from '../resource-utils-capi'
4
+ import { getGCPMachineStatus, getGCPMachineInstanceType, getGCPMachineZone, getGCPMachineInstanceID } from '../resource-utils-gcp-capi'
5
+
6
+ interface Props {
7
+ data: any
8
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
9
+ }
10
+
11
+ export function GCPMachineRenderer({ data }: Props) {
12
+ const spec = data.spec || {}
13
+ const conditions = getCAPIConditions(data)
14
+ const machineStatus = getGCPMachineStatus(data)
15
+ const isFailed = machineStatus.level === 'unhealthy'
16
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
17
+
18
+ return (
19
+ <>
20
+ {isFailed && (
21
+ <AlertBanner variant="error" title="GCP Machine Not Ready" message={readyCond?.message || 'GCPMachine is not ready.'} />
22
+ )}
23
+
24
+ <Section title="Instance" icon={Cloud}>
25
+ <PropertyList>
26
+ <Property label="Instance Type" value={getGCPMachineInstanceType(data)} />
27
+ <Property label="Zone" value={getGCPMachineZone(data)} />
28
+ <Property label="Instance ID" value={<span className="font-mono text-[11px]">{getGCPMachineInstanceID(data)}</span>} />
29
+ {spec.image && <Property label="Image" value={spec.image} />}
30
+ </PropertyList>
31
+ </Section>
32
+
33
+ {/* Disks */}
34
+ {spec.additionalDisks?.length > 0 && (
35
+ <Section title="Additional Disks" icon={Cpu}>
36
+ <table className="w-full text-xs">
37
+ <thead><tr className="text-theme-text-tertiary"><th className="text-left font-medium py-1">Size</th><th className="text-left font-medium py-1">Type</th></tr></thead>
38
+ <tbody>
39
+ {spec.additionalDisks.map((d: any, i: number) => (
40
+ <tr key={i} className="border-t border-theme-border">
41
+ <td className="py-1 text-theme-text-secondary">{d.deviceType || '-'}</td>
42
+ <td className="py-1 text-theme-text-secondary">{d.size ? `${d.size}GB` : '-'}</td>
43
+ </tr>
44
+ ))}
45
+ </tbody>
46
+ </table>
47
+ </Section>
48
+ )}
49
+
50
+ <ConditionsSection conditions={conditions} />
51
+ </>
52
+ )
53
+ }
@@ -0,0 +1,75 @@
1
+ import { Globe, Shield, Settings } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import { getCAPIConditions } from '../resource-utils-capi'
4
+ import { getGCPMCPStatus, getGCPMCPClusterName, getGCPMCPProject, getGCPMCPLocation, getGCPMCPVersion, getGCPMCPReleaseChannel, getGCPMCPAutopilot, getGCPMCPEndpoint } from '../resource-utils-gcp-capi'
5
+
6
+ interface Props {
7
+ data: any
8
+ onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
9
+ }
10
+
11
+ export function GCPManagedControlPlaneRenderer({ data }: Props) {
12
+ const spec = data.spec || {}
13
+ const conditions = getCAPIConditions(data)
14
+ const mcpStatus = getGCPMCPStatus(data)
15
+ const isFailed = mcpStatus.level === 'unhealthy'
16
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
17
+ const endpoint = getGCPMCPEndpoint(data)
18
+ const masterAuth = spec.master_authorized_networks_config
19
+
20
+ return (
21
+ <>
22
+ {isFailed && (
23
+ <AlertBanner variant="error" title="GKE Control Plane Not Ready" message={readyCond?.message || 'GCPManagedControlPlane is not ready.'} />
24
+ )}
25
+
26
+ <Section title="Overview" icon={Globe}>
27
+ <PropertyList>
28
+ <Property label="GKE Cluster" value={getGCPMCPClusterName(data)} />
29
+ <Property label="Project" value={getGCPMCPProject(data)} />
30
+ <Property label="Location" value={getGCPMCPLocation(data)} />
31
+ <Property label="Version" value={getGCPMCPVersion(data)} />
32
+ <Property label="Release Channel" value={getGCPMCPReleaseChannel(data)} />
33
+ {getGCPMCPAutopilot(data) && <Property label="Autopilot" value="Enabled" />}
34
+ {endpoint !== '-' && <Property label="Endpoint" value={<span className="font-mono text-[10px] break-all">{endpoint}</span>} />}
35
+ </PropertyList>
36
+ </Section>
37
+
38
+ {/* Cluster Network */}
39
+ {spec.clusterNetwork && (
40
+ <Section title="Network" icon={Shield}>
41
+ <PropertyList>
42
+ {spec.clusterNetwork?.pod?.cidrBlock && <Property label="Pod CIDR" value={spec.clusterNetwork.pod.cidrBlock} />}
43
+ {spec.clusterNetwork?.service?.cidrBlock && <Property label="Service CIDR" value={spec.clusterNetwork.service.cidrBlock} />}
44
+ {spec.clusterNetwork?.useIPAliases != null && <Property label="IP Aliases" value={spec.clusterNetwork.useIPAliases ? 'Enabled' : 'Disabled'} />}
45
+ </PropertyList>
46
+ </Section>
47
+ )}
48
+
49
+ {/* Services */}
50
+ {(spec.loggingService || spec.monitoringService) && (
51
+ <Section title="Services" icon={Settings}>
52
+ <PropertyList>
53
+ {spec.loggingService && <Property label="Logging" value={spec.loggingService} />}
54
+ {spec.monitoringService && <Property label="Monitoring" value={spec.monitoringService} />}
55
+ </PropertyList>
56
+ </Section>
57
+ )}
58
+
59
+ {/* Master Authorized Networks */}
60
+ {masterAuth?.cidr_blocks?.length > 0 && (
61
+ <Section title="Authorized Networks" icon={Shield}>
62
+ <div className="flex flex-wrap gap-1">
63
+ {masterAuth.cidr_blocks.map((block: any, i: number) => (
64
+ <span key={i} className="badge badge-sm bg-theme-elevated text-theme-text-secondary border-theme-border font-mono text-[10px]">
65
+ {block.display_name ? `${block.display_name}: ` : ''}{block.cidr_block}
66
+ </span>
67
+ ))}
68
+ </div>
69
+ </Section>
70
+ )}
71
+
72
+ <ConditionsSection conditions={conditions} />
73
+ </>
74
+ )
75
+ }