@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,211 @@
|
|
|
1
|
+
// AWS CAPI Infrastructure Provider utility functions
|
|
2
|
+
|
|
3
|
+
import type { StatusBadge } from './resource-utils'
|
|
4
|
+
import { healthColors } from './resource-utils'
|
|
5
|
+
import { getCAPIConditions, getCAPIReadyStatus } from './resource-utils-capi'
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// AWSManagedControlPlane
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export function getAWSMCPStatus(resource: any): StatusBadge {
|
|
12
|
+
return getCAPIReadyStatus(resource)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getAWSMCPEKSClusterName(resource: any): string {
|
|
16
|
+
return resource.spec?.eksClusterName || '-'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getAWSMCPRegion(resource: any): string {
|
|
20
|
+
return resource.spec?.region || '-'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getAWSMCPVersion(resource: any): string {
|
|
24
|
+
return resource.spec?.version || '-'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getAWSMCPEndpointAccess(resource: any): string {
|
|
28
|
+
const pub = resource.spec?.endpointAccess?.public
|
|
29
|
+
const priv = resource.spec?.endpointAccess?.private
|
|
30
|
+
if (pub && priv) return 'Public & Private'
|
|
31
|
+
if (pub) return 'Public'
|
|
32
|
+
if (priv) return 'Private'
|
|
33
|
+
return '-'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AWSAddon {
|
|
37
|
+
name: string
|
|
38
|
+
specVersion: string
|
|
39
|
+
statusVersion: string
|
|
40
|
+
status: string
|
|
41
|
+
arn: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getAWSMCPAddons(resource: any): AWSAddon[] {
|
|
45
|
+
const specAddons = resource.spec?.addons || []
|
|
46
|
+
const statusAddons = resource.status?.addons || []
|
|
47
|
+
const statusMap = new Map(statusAddons.map((a: any) => [a.name, a]))
|
|
48
|
+
|
|
49
|
+
return specAddons.map((sa: any) => {
|
|
50
|
+
const st: any = statusMap.get(sa.name) || {}
|
|
51
|
+
return {
|
|
52
|
+
name: sa.name,
|
|
53
|
+
specVersion: sa.version || '-',
|
|
54
|
+
statusVersion: st.currentVersion || st.version || '-',
|
|
55
|
+
status: st.status || 'Unknown',
|
|
56
|
+
arn: st.arn || '-',
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AWSSubnet {
|
|
62
|
+
id: string
|
|
63
|
+
az: string
|
|
64
|
+
isPublic: boolean
|
|
65
|
+
cidrBlock: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getAWSMCPSubnets(resource: any): AWSSubnet[] {
|
|
69
|
+
const subnets = resource.spec?.network?.subnets || []
|
|
70
|
+
return subnets.map((s: any) => ({
|
|
71
|
+
id: s.id || s.resourceID || '-',
|
|
72
|
+
az: s.availabilityZone || '-',
|
|
73
|
+
isPublic: !!s.isPublic,
|
|
74
|
+
cidrBlock: s.cidrBlock || '-',
|
|
75
|
+
}))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AWSSecurityGroup {
|
|
79
|
+
role: string
|
|
80
|
+
id: string
|
|
81
|
+
name: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getAWSMCPSecurityGroups(resource: any): AWSSecurityGroup[] {
|
|
85
|
+
const sgs = resource.status?.networkStatus?.securityGroups || {}
|
|
86
|
+
const result: AWSSecurityGroup[] = []
|
|
87
|
+
for (const [role, sg] of Object.entries(sgs) as [string, any][]) {
|
|
88
|
+
result.push({ role, id: sg.id || '-', name: sg.name || '-' })
|
|
89
|
+
}
|
|
90
|
+
return result
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getAWSMCPNATGatewayIPs(resource: any): string[] {
|
|
94
|
+
return resource.status?.networkStatus?.natGatewaysIPs || []
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getAWSMCPFailureDomains(resource: any): string[] {
|
|
98
|
+
const fd = resource.status?.failureDomains || {}
|
|
99
|
+
return Object.keys(fd).sort()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getAWSMCPVPC(resource: any): { id: string; cidrBlock: string } {
|
|
103
|
+
const vpc = resource.spec?.network?.vpc || {}
|
|
104
|
+
return { id: vpc.id || '-', cidrBlock: vpc.cidrBlock || '-' }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// AWSManagedMachinePool
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
export function getAWSMMPStatus(resource: any): StatusBadge {
|
|
112
|
+
return getCAPIReadyStatus(resource)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getAWSMMPInstanceType(resource: any): string {
|
|
116
|
+
return resource.spec?.instanceType || '-'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getAWSMMPCapacityType(resource: any): string {
|
|
120
|
+
return resource.spec?.capacityType || '-'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function getAWSMMPAMIType(resource: any): string {
|
|
124
|
+
return resource.spec?.amiType || '-'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function getAWSMMPNodegroupName(resource: any): string {
|
|
128
|
+
return resource.spec?.eksNodegroupName || '-'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getAWSMMPScaling(resource: any): { min: number; max: number } {
|
|
132
|
+
return {
|
|
133
|
+
min: resource.spec?.scaling?.minSize ?? 0,
|
|
134
|
+
max: resource.spec?.scaling?.maxSize ?? 0,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getAWSMMPReplicas(resource: any): string {
|
|
139
|
+
const ready = resource.status?.replicas ?? 0
|
|
140
|
+
const desired = resource.spec?.scaling?.maxSize
|
|
141
|
+
if (desired != null) return `${ready}/${desired}`
|
|
142
|
+
return String(ready)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// AWSMachine
|
|
147
|
+
// ============================================================================
|
|
148
|
+
|
|
149
|
+
export function getAWSMachineStatus(resource: any): StatusBadge {
|
|
150
|
+
const state = resource.status?.instanceState?.toLowerCase()
|
|
151
|
+
if (state === 'running') {
|
|
152
|
+
const conditions = getCAPIConditions(resource)
|
|
153
|
+
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
154
|
+
if (readyCond?.status === 'True') return { text: 'Running', color: healthColors.healthy, level: 'healthy' }
|
|
155
|
+
if (readyCond?.status === 'False') return { text: readyCond.reason || 'NotReady', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
156
|
+
return { text: 'Running', color: healthColors.healthy, level: 'healthy' }
|
|
157
|
+
}
|
|
158
|
+
if (state === 'pending') return { text: 'Pending', color: healthColors.degraded, level: 'degraded' }
|
|
159
|
+
if (state === 'terminated' || state === 'shutting-down') return { text: state, color: healthColors.unhealthy, level: 'unhealthy' }
|
|
160
|
+
if (state === 'stopping' || state === 'stopped') return { text: state, color: healthColors.degraded, level: 'degraded' }
|
|
161
|
+
return getCAPIReadyStatus(resource)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function getAWSMachineInstanceType(resource: any): string {
|
|
165
|
+
return resource.spec?.instanceType || '-'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getAWSMachineInstanceState(resource: any): string {
|
|
169
|
+
return resource.status?.instanceState || '-'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getAWSMachineInstanceID(resource: any): string {
|
|
173
|
+
return resource.spec?.instanceID || '-'
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// AWSMachineTemplate
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
export function getAWSMTInstanceType(resource: any): string {
|
|
181
|
+
return resource.spec?.template?.spec?.instanceType || '-'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function getAWSMTCapacity(resource: any): string {
|
|
185
|
+
const cap = resource.status?.capacity
|
|
186
|
+
if (!cap) return '-'
|
|
187
|
+
const parts: string[] = []
|
|
188
|
+
if (cap.cpu) parts.push(`${cap.cpu} CPU`)
|
|
189
|
+
if (cap.memory) parts.push(cap.memory)
|
|
190
|
+
return parts.join(', ') || '-'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// AWSManagedCluster
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
export function getAWSManagedClusterStatus(resource: any): StatusBadge {
|
|
198
|
+
return getCAPIReadyStatus(resource)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function getAWSManagedClusterEndpoint(resource: any): string {
|
|
202
|
+
const host = resource.spec?.controlPlaneEndpoint?.host
|
|
203
|
+
const port = resource.spec?.controlPlaneEndpoint?.port
|
|
204
|
+
if (host) return port && port !== 443 ? `${host}:${port}` : host
|
|
205
|
+
return '-'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function getAWSManagedClusterFailureDomains(resource: any): string[] {
|
|
209
|
+
const fd = resource.status?.failureDomains || {}
|
|
210
|
+
return Object.keys(fd).sort()
|
|
211
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Azure CAPI Infrastructure Provider utility functions
|
|
2
|
+
|
|
3
|
+
import type { StatusBadge } from './resource-utils'
|
|
4
|
+
import { getCAPIReadyStatus } from './resource-utils-capi'
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// AzureManagedControlPlane (AKS)
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export function getAzureMCPStatus(resource: any): StatusBadge {
|
|
11
|
+
return getCAPIReadyStatus(resource)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getAzureMCPLocation(resource: any): string {
|
|
15
|
+
return resource.spec?.location || '-'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getAzureMCPVersion(resource: any): string {
|
|
19
|
+
return resource.spec?.version || '-'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getAzureMCPResourceGroup(resource: any): string {
|
|
23
|
+
return resource.spec?.resourceGroupName || '-'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getAzureMCPSKUTier(resource: any): string {
|
|
27
|
+
return resource.spec?.sku?.tier || '-'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getAzureMCPNetworkPlugin(resource: any): string {
|
|
31
|
+
return resource.spec?.networkPlugin || '-'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getAzureMCPNetworkPolicy(resource: any): string {
|
|
35
|
+
return resource.spec?.networkPolicy || '-'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getAzureMCPDNSPrefix(resource: any): string {
|
|
39
|
+
return resource.spec?.dnsPrefix || '-'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getAzureMCPUpgradeChannel(resource: any): string {
|
|
43
|
+
return resource.spec?.autoUpgradeProfile?.upgradeChannel || '-'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getAzureMCPPrivateCluster(resource: any): boolean {
|
|
47
|
+
return !!resource.spec?.apiServerAccessProfile?.enablePrivateCluster
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// AzureManagedMachinePool (AKS node pool)
|
|
52
|
+
// ============================================================================
|
|
53
|
+
|
|
54
|
+
export function getAzureMMPStatus(resource: any): StatusBadge {
|
|
55
|
+
return getCAPIReadyStatus(resource)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getAzureMMPSKU(resource: any): string {
|
|
59
|
+
return resource.spec?.sku || '-'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getAzureMMPMode(resource: any): string {
|
|
63
|
+
return resource.spec?.mode || '-'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getAzureMMPOSDiskInfo(resource: any): string {
|
|
67
|
+
const diskType = resource.spec?.osDiskType || 'Managed'
|
|
68
|
+
const diskSize = resource.spec?.osDiskSizeGB
|
|
69
|
+
if (diskSize) return `${diskType} ${diskSize}GB`
|
|
70
|
+
return diskType
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getAzureMMPScaling(resource: any): { min: number; max: number } {
|
|
74
|
+
const scaling = resource.spec?.scaling || {}
|
|
75
|
+
return {
|
|
76
|
+
min: scaling.minSize ?? 0,
|
|
77
|
+
max: scaling.maxSize ?? 0,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getAzureMMPReplicas(resource: any): string {
|
|
82
|
+
return String(resource.status?.replicas ?? 0)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getAzureMMPScaleSetPriority(resource: any): string {
|
|
86
|
+
return resource.spec?.scaleSetPriority || 'Regular'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getAzureMMPOSType(resource: any): string {
|
|
90
|
+
return resource.spec?.osType || 'Linux'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// AzureMachine
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
export function getAzureMachineStatus(resource: any): StatusBadge {
|
|
98
|
+
return getCAPIReadyStatus(resource)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getAzureMachineVMSize(resource: any): string {
|
|
102
|
+
return resource.spec?.vmSize || '-'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getAzureMachineProviderID(resource: any): string {
|
|
106
|
+
return resource.spec?.providerID || '-'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// AzureMachineTemplate
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
export function getAzureMTVMSize(resource: any): string {
|
|
114
|
+
return resource.spec?.template?.spec?.vmSize || '-'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// AzureManagedCluster
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
export function getAzureManagedClusterStatus(resource: any): StatusBadge {
|
|
122
|
+
return getCAPIReadyStatus(resource)
|
|
123
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// Cluster API (CAPI) CRD utility functions
|
|
2
|
+
|
|
3
|
+
import type { StatusBadge } from './resource-utils'
|
|
4
|
+
import { healthColors } from './resource-utils'
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// SHARED CAPI UTILITIES
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
// CAPI phase-to-health mapping
|
|
11
|
+
const PHASE_MAP: Record<string, { text: string; level: StatusBadge['level'] }> = {
|
|
12
|
+
provisioned: { text: 'Provisioned', level: 'healthy' },
|
|
13
|
+
running: { text: 'Running', level: 'healthy' },
|
|
14
|
+
scaled: { text: 'Scaled', level: 'healthy' },
|
|
15
|
+
provisioning: { text: 'Provisioning', level: 'degraded' },
|
|
16
|
+
pending: { text: 'Pending', level: 'degraded' },
|
|
17
|
+
scaling: { text: 'Scaling', level: 'degraded' },
|
|
18
|
+
upgrading: { text: 'Upgrading', level: 'degraded' },
|
|
19
|
+
deleting: { text: 'Deleting', level: 'degraded' },
|
|
20
|
+
failed: { text: 'Failed', level: 'unhealthy' },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getCAPIConditions(resource: any): any[] {
|
|
24
|
+
// v1beta2 uses status.v1beta2.conditions, v1beta1 uses status.conditions
|
|
25
|
+
return resource.status?.v1beta2?.conditions || resource.status?.conditions || []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getCAPIReadyCondition(resource: any): any | undefined {
|
|
29
|
+
const conditions = getCAPIConditions(resource)
|
|
30
|
+
return conditions.find((c: any) => c.type === 'Ready') || conditions.find((c: any) => c.type === 'Available')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getCAPIPhaseStatus(resource: any): StatusBadge {
|
|
34
|
+
const phase = resource.status?.phase?.toLowerCase()
|
|
35
|
+
if (phase && PHASE_MAP[phase]) {
|
|
36
|
+
const m = PHASE_MAP[phase]
|
|
37
|
+
return { text: m.text, color: healthColors[m.level], level: m.level }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Fall back to Ready condition
|
|
41
|
+
const readyCond = getCAPIReadyCondition(resource)
|
|
42
|
+
if (readyCond?.status === 'True') {
|
|
43
|
+
return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
|
|
44
|
+
}
|
|
45
|
+
if (readyCond?.status === 'False') {
|
|
46
|
+
return { text: readyCond.reason || 'NotReady', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
47
|
+
}
|
|
48
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getCAPIReadyStatus(resource: any): StatusBadge {
|
|
52
|
+
const readyCond = getCAPIReadyCondition(resource)
|
|
53
|
+
if (readyCond?.status === 'True') {
|
|
54
|
+
return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
|
|
55
|
+
}
|
|
56
|
+
if (readyCond?.status === 'False') {
|
|
57
|
+
return { text: readyCond.reason || 'NotReady', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
58
|
+
}
|
|
59
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Normalize v1beta1 updatedReplicas vs v1beta2 upToDateReplicas
|
|
63
|
+
function getUpToDateReplicas(resource: any): number {
|
|
64
|
+
return resource.status?.upToDateReplicas ?? resource.status?.updatedReplicas ?? 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// CAPI CLUSTER UTILITIES
|
|
69
|
+
// ============================================================================
|
|
70
|
+
|
|
71
|
+
export function getClusterStatus(resource: any): StatusBadge {
|
|
72
|
+
return getCAPIPhaseStatus(resource)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getClusterClass(resource: any): string {
|
|
76
|
+
return resource.spec?.topology?.class || '-'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getClusterVersion(resource: any): string {
|
|
80
|
+
return resource.spec?.topology?.version || '-'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getClusterCPReplicas(resource: any): string {
|
|
84
|
+
// v1beta2: status.controlPlane.readyReplicas / status.controlPlane.desiredReplicas
|
|
85
|
+
const cpReady = resource.status?.controlPlane?.readyReplicas ?? resource.status?.controlPlaneReady
|
|
86
|
+
const cpDesired = resource.status?.controlPlane?.desiredReplicas
|
|
87
|
+
if (cpDesired != null) return `${cpReady ?? 0}/${cpDesired}`
|
|
88
|
+
return typeof cpReady === 'boolean' ? (cpReady ? 'Ready' : 'NotReady') : '-'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getClusterWorkerReplicas(resource: any): string {
|
|
92
|
+
// v1beta2: status.workers.readyReplicas / status.workers.desiredReplicas
|
|
93
|
+
const wReady = resource.status?.workers?.readyReplicas ?? resource.status?.workersReady
|
|
94
|
+
const wDesired = resource.status?.workers?.desiredReplicas
|
|
95
|
+
if (wDesired != null) return `${wReady ?? 0}/${wDesired}`
|
|
96
|
+
return typeof wReady === 'boolean' ? (wReady ? 'Ready' : 'NotReady') : '-'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getClusterEndpoint(resource: any): string {
|
|
100
|
+
const host = resource.spec?.controlPlaneEndpoint?.host
|
|
101
|
+
const port = resource.spec?.controlPlaneEndpoint?.port
|
|
102
|
+
if (host) return port ? `${host}:${port}` : host
|
|
103
|
+
return '-'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// CAPI MACHINE UTILITIES
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
export function getMachineStatus(resource: any): StatusBadge {
|
|
111
|
+
return getCAPIPhaseStatus(resource)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function getMachineRole(resource: any): string {
|
|
115
|
+
const labels = resource.metadata?.labels || {}
|
|
116
|
+
if (labels['cluster.x-k8s.io/control-plane'] !== undefined) return 'Control Plane'
|
|
117
|
+
if (labels['cluster.x-k8s.io/control-plane-name']) return 'Control Plane'
|
|
118
|
+
return 'Worker'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getMachineClusterName(resource: any): string {
|
|
122
|
+
return resource.metadata?.labels?.['cluster.x-k8s.io/cluster-name'] || resource.spec?.clusterName || '-'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function getMachineNodeRef(resource: any): string {
|
|
126
|
+
return resource.status?.nodeRef?.name || '-'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function getMachineVersion(resource: any): string {
|
|
130
|
+
return resource.spec?.version || '-'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getMachineProviderID(resource: any): string {
|
|
134
|
+
return resource.spec?.providerID || '-'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// PROVIDER DETECTION UTILITIES
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
export interface InfraProvider {
|
|
142
|
+
provider: string // 'AWS' | 'GCP' | 'Azure' | 'vSphere' | 'Docker' | unknown
|
|
143
|
+
region?: string // availability zone or region
|
|
144
|
+
instanceId?: string // cloud instance identifier
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Parse spec.providerID to extract cloud provider, region, and instance ID */
|
|
148
|
+
export function parseProviderID(providerID: string): InfraProvider | null {
|
|
149
|
+
if (!providerID || providerID === '-') return null
|
|
150
|
+
|
|
151
|
+
// AWS: aws:///us-east-1a/i-0abcdef1234567890 (or aws://us-east-1a/...)
|
|
152
|
+
if (providerID.startsWith('aws://')) {
|
|
153
|
+
const parts = providerID.replace(/^aws:\/\/\/?/, '').split('/')
|
|
154
|
+
return { provider: 'AWS', region: parts[0] || undefined, instanceId: parts[1] || undefined }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// GCP: gce:///my-project/us-central1-a/my-instance (or gce://...)
|
|
158
|
+
if (providerID.startsWith('gce://')) {
|
|
159
|
+
const parts = providerID.replace(/^gce:\/\/\/?/, '').split('/')
|
|
160
|
+
return { provider: 'GCP', region: parts[1] || undefined, instanceId: parts[2] || undefined }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Azure: azure:///subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{name}
|
|
164
|
+
if (providerID.startsWith('azure://')) {
|
|
165
|
+
const vmMatch = providerID.match(/virtualMachines\/([^/]+)/)
|
|
166
|
+
const rgMatch = providerID.match(/resourceGroups\/([^/]+)/)
|
|
167
|
+
return { provider: 'Azure', region: rgMatch?.[1], instanceId: vmMatch?.[1] }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// vSphere: vsphere://42305a8e-...
|
|
171
|
+
if (providerID.startsWith('vsphere://')) {
|
|
172
|
+
return { provider: 'vSphere', instanceId: providerID.replace(/^vsphere:\/\/\/?/, '') }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Docker (CAPD): docker:///container-name (or docker://...)
|
|
176
|
+
if (providerID.startsWith('docker://')) {
|
|
177
|
+
return { provider: 'Docker', instanceId: providerID.replace(/^docker:\/\/\/?/, '') }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { provider: providerID.split(':')[0] || 'Unknown' }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Detect provider from an infrastructureRef kind name */
|
|
184
|
+
export function getProviderFromInfraKind(kind: string): string {
|
|
185
|
+
const k = kind.toLowerCase()
|
|
186
|
+
if (k.startsWith('aws') || k.startsWith('ec2')) return 'AWS'
|
|
187
|
+
if (k.startsWith('gcp') || k.startsWith('gce')) return 'GCP'
|
|
188
|
+
if (k.startsWith('azure') || k.startsWith('aks')) return 'Azure'
|
|
189
|
+
if (k.startsWith('vsphere')) return 'vSphere'
|
|
190
|
+
if (k.startsWith('docker')) return 'Docker'
|
|
191
|
+
if (k.startsWith('metal') || k.startsWith('byoh')) return 'Bare Metal'
|
|
192
|
+
return kind // Return raw kind as fallback
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// CAPI MACHINEDEPLOYMENT UTILITIES
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
export function getMachineDeploymentStatus(resource: any): StatusBadge {
|
|
200
|
+
return getCAPIPhaseStatus(resource)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function getMachineDeploymentReplicas(resource: any): string {
|
|
204
|
+
const desired = resource.spec?.replicas ?? 0
|
|
205
|
+
const ready = resource.status?.readyReplicas ?? 0
|
|
206
|
+
return `${ready}/${desired}`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function getMachineDeploymentVersion(resource: any): string {
|
|
210
|
+
return resource.spec?.template?.spec?.version || '-'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function getMachineDeploymentUpToDate(resource: any): string {
|
|
214
|
+
return String(getUpToDateReplicas(resource))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// CAPI KUBEADMCONTROLPLANE UTILITIES
|
|
219
|
+
// ============================================================================
|
|
220
|
+
|
|
221
|
+
export function getKCPStatus(resource: any): StatusBadge {
|
|
222
|
+
return getCAPIReadyStatus(resource)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function getKCPReplicas(resource: any): string {
|
|
226
|
+
const desired = resource.spec?.replicas ?? 0
|
|
227
|
+
const ready = resource.status?.readyReplicas ?? 0
|
|
228
|
+
return `${ready}/${desired}`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function getKCPVersion(resource: any): string {
|
|
232
|
+
return resource.spec?.version || '-'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function getKCPInitialized(resource: any): boolean {
|
|
236
|
+
// v1beta2: status.initialization.controlPlaneInitialized; v1beta1: status.initialized
|
|
237
|
+
return resource.status?.initialization?.controlPlaneInitialized ?? resource.status?.initialized ?? false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// CAPI MACHINESET UTILITIES
|
|
242
|
+
// ============================================================================
|
|
243
|
+
|
|
244
|
+
export function getMachineSetStatus(resource: any): StatusBadge {
|
|
245
|
+
return getCAPIPhaseStatus(resource)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function getMachineSetReplicas(resource: any): string {
|
|
249
|
+
const desired = resource.spec?.replicas ?? 0
|
|
250
|
+
const ready = resource.status?.readyReplicas ?? 0
|
|
251
|
+
return `${ready}/${desired}`
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// CAPI MACHINEPOOL UTILITIES
|
|
256
|
+
// ============================================================================
|
|
257
|
+
|
|
258
|
+
export function getMachinePoolStatus(resource: any): StatusBadge {
|
|
259
|
+
return getCAPIPhaseStatus(resource)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function getMachinePoolReplicas(resource: any): string {
|
|
263
|
+
const desired = resource.spec?.replicas ?? 0
|
|
264
|
+
const ready = resource.status?.readyReplicas ?? 0
|
|
265
|
+
return `${ready}/${desired}`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ============================================================================
|
|
269
|
+
// CAPI CLUSTERCLASS UTILITIES
|
|
270
|
+
// ============================================================================
|
|
271
|
+
|
|
272
|
+
export function getClusterClassStatus(resource: any): StatusBadge {
|
|
273
|
+
return getCAPIReadyStatus(resource)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================================================
|
|
277
|
+
// CAPI MACHINEHEALTHCHECK UTILITIES
|
|
278
|
+
// ============================================================================
|
|
279
|
+
|
|
280
|
+
export function getMachineHealthCheckStatus(resource: any): StatusBadge {
|
|
281
|
+
return getCAPIReadyStatus(resource)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function getMachineHealthCheckClusterName(resource: any): string {
|
|
285
|
+
return resource.spec?.clusterName || resource.metadata?.labels?.['cluster.x-k8s.io/cluster-name'] || '-'
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function getMachineHealthCheckHealthy(resource: any): string {
|
|
289
|
+
const expected = resource.status?.expectedMachines ?? 0
|
|
290
|
+
const healthy = resource.status?.currentHealthy ?? 0
|
|
291
|
+
return `${healthy}/${expected}`
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function getClusterProvider(resource: any): string {
|
|
295
|
+
const infraKind = resource.spec?.infrastructureRef?.kind || ''
|
|
296
|
+
if (infraKind) return getProviderFromInfraKind(infraKind)
|
|
297
|
+
return '-'
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Parse CAPI compound condition messages ("* Foo: bar * Baz: qux") into a structured list */
|
|
301
|
+
export function parseCAPIConditionMessage(message: string): string[] | null {
|
|
302
|
+
if (!message || !message.includes('*')) return null
|
|
303
|
+
const items = message.split(/\s*\*\s*/).filter(Boolean).map(s => s.trim())
|
|
304
|
+
return items.length > 1 ? items : null
|
|
305
|
+
}
|