@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.
- package/package.json +1 -1
- package/src/components/resources/ResourcesView.tsx +267 -14
- package/src/components/resources/renderers/AWSMachineRenderer.tsx +96 -0
- package/src/components/resources/renderers/AWSMachineTemplateRenderer.tsx +39 -0
- package/src/components/resources/renderers/AWSManagedClusterRenderer.tsx +49 -0
- package/src/components/resources/renderers/AWSManagedControlPlaneRenderer.tsx +174 -0
- package/src/components/resources/renderers/AWSManagedMachinePoolRenderer.tsx +89 -0
- package/src/components/resources/renderers/AzureMachineRenderer.tsx +41 -0
- package/src/components/resources/renderers/AzureManagedControlPlaneRenderer.tsx +71 -0
- package/src/components/resources/renderers/AzureManagedMachinePoolRenderer.tsx +106 -0
- package/src/components/resources/renderers/CAPIClusterClassRenderer.tsx +156 -0
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +240 -0
- package/src/components/resources/renderers/CAPIKubeadmConfigRenderer.tsx +97 -0
- package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +124 -0
- package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +128 -0
- package/src/components/resources/renderers/CAPIMachineDrainRuleRenderer.tsx +38 -0
- package/src/components/resources/renderers/CAPIMachineHealthCheckRenderer.tsx +146 -0
- package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +92 -0
- package/src/components/resources/renderers/CAPIMachineRenderer.tsx +170 -0
- package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +101 -0
- package/src/components/resources/renderers/GCPMachineRenderer.tsx +53 -0
- package/src/components/resources/renderers/GCPManagedControlPlaneRenderer.tsx +75 -0
- package/src/components/resources/renderers/GCPManagedMachinePoolRenderer.tsx +102 -0
- package/src/components/resources/renderers/aws-capi-cells.tsx +117 -0
- package/src/components/resources/renderers/azure-capi-cells.tsx +83 -0
- package/src/components/resources/renderers/capi-cells.tsx +158 -0
- package/src/components/resources/renderers/gcp-capi-cells.tsx +68 -0
- package/src/components/resources/renderers/index.ts +25 -0
- package/src/components/resources/resource-utils-aws-capi.ts +211 -0
- package/src/components/resources/resource-utils-azure-capi.ts +123 -0
- package/src/components/resources/resource-utils-capi.ts +305 -0
- package/src/components/resources/resource-utils-gcp-capi.ts +130 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +97 -2
- package/src/components/topology/K8sResourceNode.tsx +8 -0
- package/src/components/topology/TopologyControls.tsx +16 -0
- package/src/components/topology/TopologyFilterSidebar.tsx +15 -0
- package/src/components/topology/TopologyGraph.tsx +2 -1
- package/src/components/topology/layout.ts +11 -0
- package/src/components/topology/topology.css +29 -0
- package/src/components/ui/Badge.tsx +10 -0
- package/src/components/ui/drawer-components.tsx +54 -17
- package/src/types/core.ts +18 -2
- package/src/utils/api-resources.ts +4 -0
- 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
|
+
}
|