@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,174 @@
|
|
|
1
|
+
import { Globe, Network, Shield, Server, Package } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
3
|
+
import { getCAPIConditions } from '../resource-utils-capi'
|
|
4
|
+
import {
|
|
5
|
+
getAWSMCPStatus, getAWSMCPEKSClusterName, getAWSMCPRegion, getAWSMCPVersion,
|
|
6
|
+
getAWSMCPEndpointAccess, getAWSMCPAddons, getAWSMCPSubnets, getAWSMCPSecurityGroups,
|
|
7
|
+
getAWSMCPNATGatewayIPs, getAWSMCPFailureDomains, getAWSMCPVPC,
|
|
8
|
+
} from '../resource-utils-aws-capi'
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
data: any
|
|
12
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AWSManagedControlPlaneRenderer({ data }: Props) {
|
|
16
|
+
const spec = data.spec || {}
|
|
17
|
+
const conditions = getCAPIConditions(data)
|
|
18
|
+
|
|
19
|
+
const mcpStatus = getAWSMCPStatus(data)
|
|
20
|
+
const isFailed = mcpStatus.level === 'unhealthy'
|
|
21
|
+
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
22
|
+
|
|
23
|
+
const vpc = getAWSMCPVPC(data)
|
|
24
|
+
const subnets = getAWSMCPSubnets(data)
|
|
25
|
+
const securityGroups = getAWSMCPSecurityGroups(data)
|
|
26
|
+
const natIPs = getAWSMCPNATGatewayIPs(data)
|
|
27
|
+
const failureDomains = getAWSMCPFailureDomains(data)
|
|
28
|
+
const addons = getAWSMCPAddons(data)
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
{isFailed && (
|
|
33
|
+
<AlertBanner
|
|
34
|
+
variant="error"
|
|
35
|
+
title="EKS Control Plane Not Ready"
|
|
36
|
+
message={readyCond?.message || 'AWSManagedControlPlane is not ready.'}
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
<Section title="Overview" icon={Globe}>
|
|
41
|
+
<PropertyList>
|
|
42
|
+
<Property label="EKS Cluster" value={getAWSMCPEKSClusterName(data)} />
|
|
43
|
+
<Property label="Region" value={getAWSMCPRegion(data)} />
|
|
44
|
+
<Property label="Version" value={getAWSMCPVersion(data)} />
|
|
45
|
+
<Property label="Endpoint Access" value={getAWSMCPEndpointAccess(data)} />
|
|
46
|
+
{spec.roleName && <Property label="IAM Role" value={spec.roleName} />}
|
|
47
|
+
{spec.identityRef?.name && (
|
|
48
|
+
<Property label="Identity" value={`${spec.identityRef.kind}/${spec.identityRef.name}`} />
|
|
49
|
+
)}
|
|
50
|
+
</PropertyList>
|
|
51
|
+
</Section>
|
|
52
|
+
|
|
53
|
+
{/* Network - VPC */}
|
|
54
|
+
{vpc.id !== '-' && (
|
|
55
|
+
<Section title="VPC" icon={Network}>
|
|
56
|
+
<PropertyList>
|
|
57
|
+
<Property label="VPC ID" value={<span className="font-mono text-[11px]">{vpc.id}</span>} />
|
|
58
|
+
{vpc.cidrBlock !== '-' && <Property label="CIDR" value={vpc.cidrBlock} />}
|
|
59
|
+
</PropertyList>
|
|
60
|
+
</Section>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
{/* Subnets */}
|
|
64
|
+
{subnets.length > 0 && (
|
|
65
|
+
<Section title={`Subnets (${subnets.length})`} icon={Network}>
|
|
66
|
+
<table className="w-full text-xs">
|
|
67
|
+
<thead>
|
|
68
|
+
<tr className="text-theme-text-tertiary">
|
|
69
|
+
<th className="text-left font-medium py-1">ID</th>
|
|
70
|
+
<th className="text-left font-medium py-1">AZ</th>
|
|
71
|
+
<th className="text-left font-medium py-1">Type</th>
|
|
72
|
+
<th className="text-left font-medium py-1">CIDR</th>
|
|
73
|
+
</tr>
|
|
74
|
+
</thead>
|
|
75
|
+
<tbody>
|
|
76
|
+
{subnets.map((s, i) => (
|
|
77
|
+
<tr key={i} className="border-t border-theme-border">
|
|
78
|
+
<td className="py-1 text-theme-text-secondary font-mono text-[10px]">{s.id}</td>
|
|
79
|
+
<td className="py-1 text-theme-text-secondary">{s.az}</td>
|
|
80
|
+
<td className="py-1">
|
|
81
|
+
<span className={`badge badge-sm ${s.isPublic
|
|
82
|
+
? 'bg-sky-100 text-sky-700 border-sky-300 dark:bg-sky-950/50 dark:text-sky-400 dark:border-sky-700/40'
|
|
83
|
+
: 'bg-slate-100 text-slate-600 border-slate-300 dark:bg-slate-900/50 dark:text-slate-400 dark:border-slate-700/40'
|
|
84
|
+
}`}>{s.isPublic ? 'Public' : 'Private'}</span>
|
|
85
|
+
</td>
|
|
86
|
+
<td className="py-1 text-theme-text-secondary font-mono text-[10px]">{s.cidrBlock}</td>
|
|
87
|
+
</tr>
|
|
88
|
+
))}
|
|
89
|
+
</tbody>
|
|
90
|
+
</table>
|
|
91
|
+
</Section>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{/* Security Groups */}
|
|
95
|
+
{securityGroups.length > 0 && (
|
|
96
|
+
<Section title="Security Groups" icon={Shield}>
|
|
97
|
+
<table className="w-full text-xs">
|
|
98
|
+
<thead>
|
|
99
|
+
<tr className="text-theme-text-tertiary">
|
|
100
|
+
<th className="text-left font-medium py-1">Role</th>
|
|
101
|
+
<th className="text-left font-medium py-1">ID</th>
|
|
102
|
+
<th className="text-left font-medium py-1">Name</th>
|
|
103
|
+
</tr>
|
|
104
|
+
</thead>
|
|
105
|
+
<tbody>
|
|
106
|
+
{securityGroups.map((sg, i) => (
|
|
107
|
+
<tr key={i} className="border-t border-theme-border">
|
|
108
|
+
<td className="py-1 text-theme-text-secondary font-medium">{sg.role}</td>
|
|
109
|
+
<td className="py-1 text-theme-text-secondary font-mono text-[10px]">{sg.id}</td>
|
|
110
|
+
<td className="py-1 text-theme-text-secondary text-[10px] break-all">{sg.name}</td>
|
|
111
|
+
</tr>
|
|
112
|
+
))}
|
|
113
|
+
</tbody>
|
|
114
|
+
</table>
|
|
115
|
+
</Section>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{/* NAT Gateways */}
|
|
119
|
+
{natIPs.length > 0 && (
|
|
120
|
+
<Section title="NAT Gateways" icon={Network}>
|
|
121
|
+
<div className="flex flex-wrap gap-1">
|
|
122
|
+
{natIPs.map((ip, i) => (
|
|
123
|
+
<span key={i} className="badge badge-sm bg-theme-elevated text-theme-text-secondary border-theme-border font-mono text-[10px]">{ip}</span>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
</Section>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{/* Addons */}
|
|
130
|
+
{addons.length > 0 && (
|
|
131
|
+
<Section title="EKS Addons" icon={Package}>
|
|
132
|
+
<table className="w-full text-xs">
|
|
133
|
+
<thead>
|
|
134
|
+
<tr className="text-theme-text-tertiary">
|
|
135
|
+
<th className="text-left font-medium py-1">Name</th>
|
|
136
|
+
<th className="text-left font-medium py-1">Version</th>
|
|
137
|
+
<th className="text-left font-medium py-1">Status</th>
|
|
138
|
+
</tr>
|
|
139
|
+
</thead>
|
|
140
|
+
<tbody>
|
|
141
|
+
{addons.map((a, i) => (
|
|
142
|
+
<tr key={i} className="border-t border-theme-border">
|
|
143
|
+
<td className="py-1 text-theme-text-secondary font-medium">{a.name}</td>
|
|
144
|
+
<td className="py-1 text-theme-text-secondary font-mono text-[10px]">{a.statusVersion !== '-' ? a.statusVersion : a.specVersion}</td>
|
|
145
|
+
<td className="py-1">
|
|
146
|
+
<span className={`badge badge-sm ${a.status === 'ACTIVE'
|
|
147
|
+
? 'status-healthy'
|
|
148
|
+
: a.status === 'DEGRADED'
|
|
149
|
+
? 'status-unhealthy'
|
|
150
|
+
: 'status-neutral'
|
|
151
|
+
}`}>{a.status}</span>
|
|
152
|
+
</td>
|
|
153
|
+
</tr>
|
|
154
|
+
))}
|
|
155
|
+
</tbody>
|
|
156
|
+
</table>
|
|
157
|
+
</Section>
|
|
158
|
+
)}
|
|
159
|
+
|
|
160
|
+
{/* Failure Domains */}
|
|
161
|
+
{failureDomains.length > 0 && (
|
|
162
|
+
<Section title="Failure Domains" icon={Server}>
|
|
163
|
+
<div className="flex flex-wrap gap-1">
|
|
164
|
+
{failureDomains.map((az) => (
|
|
165
|
+
<span key={az} className="badge badge-sm bg-theme-elevated text-theme-text-secondary border-theme-border">{az}</span>
|
|
166
|
+
))}
|
|
167
|
+
</div>
|
|
168
|
+
</Section>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
<ConditionsSection conditions={conditions} />
|
|
172
|
+
</>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Server, Settings } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
import { getCAPIConditions } from '../resource-utils-capi'
|
|
5
|
+
import { getAWSMMPStatus, getAWSMMPInstanceType, getAWSMMPCapacityType, getAWSMMPAMIType, getAWSMMPNodegroupName, getAWSMMPScaling } from '../resource-utils-aws-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 AWSManagedMachinePoolRenderer({ data }: Props) {
|
|
13
|
+
const spec = data.spec || {}
|
|
14
|
+
const status = data.status || {}
|
|
15
|
+
const conditions = getCAPIConditions(data)
|
|
16
|
+
|
|
17
|
+
const mmpStatus = getAWSMMPStatus(data)
|
|
18
|
+
const isFailed = mmpStatus.level === 'unhealthy'
|
|
19
|
+
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
20
|
+
|
|
21
|
+
const scaling = getAWSMMPScaling(data)
|
|
22
|
+
const capacityType = getAWSMMPCapacityType(data)
|
|
23
|
+
const labels = spec.labels || {}
|
|
24
|
+
const subnetIDs = spec.subnetIDs || []
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
{isFailed && (
|
|
29
|
+
<AlertBanner
|
|
30
|
+
variant="error"
|
|
31
|
+
title="Managed Machine Pool Not Ready"
|
|
32
|
+
message={readyCond?.message || 'AWSManagedMachinePool is not ready.'}
|
|
33
|
+
/>
|
|
34
|
+
)}
|
|
35
|
+
|
|
36
|
+
<Section title="Overview" icon={Server}>
|
|
37
|
+
<PropertyList>
|
|
38
|
+
<Property label="Node Group" value={getAWSMMPNodegroupName(data)} />
|
|
39
|
+
<Property label="Instance Type" value={getAWSMMPInstanceType(data)} />
|
|
40
|
+
<Property label="AMI Type" value={getAWSMMPAMIType(data)} />
|
|
41
|
+
<Property label="Capacity Type" value={
|
|
42
|
+
<span className={clsx('badge badge-sm', capacityType === 'spot'
|
|
43
|
+
? 'bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950/50 dark:text-amber-400 dark:border-amber-700/40'
|
|
44
|
+
: 'bg-sky-100 text-sky-700 border-sky-300 dark:bg-sky-950/50 dark:text-sky-400 dark:border-sky-700/40'
|
|
45
|
+
)}>{capacityType === 'onDemand' ? 'On-Demand' : capacityType}</span>
|
|
46
|
+
} />
|
|
47
|
+
{spec.roleName && <Property label="IAM Role" value={spec.roleName} />}
|
|
48
|
+
</PropertyList>
|
|
49
|
+
</Section>
|
|
50
|
+
|
|
51
|
+
<Section title="Scaling" icon={Settings}>
|
|
52
|
+
<PropertyList>
|
|
53
|
+
<Property label="Min Size" value={String(scaling.min)} />
|
|
54
|
+
<Property label="Max Size" value={String(scaling.max)} />
|
|
55
|
+
<Property label="Current Replicas" value={String(status.replicas ?? 0)} />
|
|
56
|
+
{spec.updateConfig?.maxUnavailable != null && (
|
|
57
|
+
<Property label="Max Unavailable" value={String(spec.updateConfig.maxUnavailable)} />
|
|
58
|
+
)}
|
|
59
|
+
</PropertyList>
|
|
60
|
+
</Section>
|
|
61
|
+
|
|
62
|
+
{/* Subnets */}
|
|
63
|
+
{subnetIDs.length > 0 && (
|
|
64
|
+
<Section title="Subnets" icon={Server}>
|
|
65
|
+
<div className="flex flex-wrap gap-1">
|
|
66
|
+
{subnetIDs.map((id: string) => (
|
|
67
|
+
<span key={id} className="badge badge-sm bg-theme-elevated text-theme-text-secondary border-theme-border font-mono text-[10px]">{id}</span>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</Section>
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{/* Labels */}
|
|
74
|
+
{Object.keys(labels).length > 0 && (
|
|
75
|
+
<Section title="Node Labels" icon={Settings}>
|
|
76
|
+
<div className="flex flex-wrap gap-1">
|
|
77
|
+
{Object.entries(labels).map(([k, v]) => (
|
|
78
|
+
<span key={k} className="badge badge-sm bg-theme-elevated text-theme-text-secondary border-theme-border text-[10px]">
|
|
79
|
+
{k}={v as string}
|
|
80
|
+
</span>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
</Section>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
<ConditionsSection conditions={conditions} />
|
|
87
|
+
</>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Cloud } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
3
|
+
import { getCAPIConditions } from '../resource-utils-capi'
|
|
4
|
+
import { getAzureMachineStatus, getAzureMachineVMSize, getAzureMachineProviderID } from '../resource-utils-azure-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 AzureMachineRenderer({ data }: Props) {
|
|
12
|
+
const spec = data.spec || {}
|
|
13
|
+
const conditions = getCAPIConditions(data)
|
|
14
|
+
const machineStatus = getAzureMachineStatus(data)
|
|
15
|
+
const isFailed = machineStatus.level === 'unhealthy'
|
|
16
|
+
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
17
|
+
const providerID = getAzureMachineProviderID(data)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{isFailed && (
|
|
22
|
+
<AlertBanner variant="error" title="Azure Machine Not Ready" message={readyCond?.message || 'AzureMachine is not ready.'} />
|
|
23
|
+
)}
|
|
24
|
+
|
|
25
|
+
<Section title="Instance" icon={Cloud}>
|
|
26
|
+
<PropertyList>
|
|
27
|
+
<Property label="VM Size" value={getAzureMachineVMSize(data)} />
|
|
28
|
+
{spec.failureDomain && <Property label="Availability Zone" value={spec.failureDomain} />}
|
|
29
|
+
{spec.osDisk?.osType && <Property label="OS Type" value={spec.osDisk.osType} />}
|
|
30
|
+
{spec.osDisk?.diskSizeGB && <Property label="OS Disk" value={`${spec.osDisk.diskSizeGB}GB`} />}
|
|
31
|
+
{providerID !== '-' && (
|
|
32
|
+
<Property label="Provider ID" value={<span className="font-mono text-[10px] break-all">{providerID}</span>} />
|
|
33
|
+
)}
|
|
34
|
+
{spec.subnetName && <Property label="Subnet" value={spec.subnetName} />}
|
|
35
|
+
</PropertyList>
|
|
36
|
+
</Section>
|
|
37
|
+
|
|
38
|
+
<ConditionsSection conditions={conditions} />
|
|
39
|
+
</>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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 { getAzureMCPStatus, getAzureMCPLocation, getAzureMCPVersion, getAzureMCPResourceGroup, getAzureMCPSKUTier, getAzureMCPNetworkPlugin, getAzureMCPNetworkPolicy, getAzureMCPDNSPrefix, getAzureMCPUpgradeChannel, getAzureMCPPrivateCluster } from '../resource-utils-azure-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 AzureManagedControlPlaneRenderer({ data }: Props) {
|
|
12
|
+
const spec = data.spec || {}
|
|
13
|
+
const conditions = getCAPIConditions(data)
|
|
14
|
+
const mcpStatus = getAzureMCPStatus(data)
|
|
15
|
+
const isFailed = mcpStatus.level === 'unhealthy'
|
|
16
|
+
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
17
|
+
const apiAccess = spec.apiServerAccessProfile || {}
|
|
18
|
+
const authorizedRanges = apiAccess.authorizedIPRanges || []
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
{isFailed && (
|
|
23
|
+
<AlertBanner variant="error" title="AKS Control Plane Not Ready" message={readyCond?.message || 'AzureManagedControlPlane is not ready.'} />
|
|
24
|
+
)}
|
|
25
|
+
|
|
26
|
+
<Section title="Overview" icon={Globe}>
|
|
27
|
+
<PropertyList>
|
|
28
|
+
<Property label="Location" value={getAzureMCPLocation(data)} />
|
|
29
|
+
<Property label="Resource Group" value={getAzureMCPResourceGroup(data)} />
|
|
30
|
+
<Property label="Version" value={getAzureMCPVersion(data)} />
|
|
31
|
+
<Property label="SKU Tier" value={getAzureMCPSKUTier(data)} />
|
|
32
|
+
{spec.dnsPrefix && <Property label="DNS Prefix" value={getAzureMCPDNSPrefix(data)} />}
|
|
33
|
+
{spec.subscriptionID && <Property label="Subscription" value={<span className="font-mono text-[10px]">{spec.subscriptionID}</span>} />}
|
|
34
|
+
</PropertyList>
|
|
35
|
+
</Section>
|
|
36
|
+
|
|
37
|
+
{/* Networking */}
|
|
38
|
+
<Section title="Networking" icon={Shield}>
|
|
39
|
+
<PropertyList>
|
|
40
|
+
<Property label="Network Plugin" value={getAzureMCPNetworkPlugin(data)} />
|
|
41
|
+
<Property label="Network Policy" value={getAzureMCPNetworkPolicy(data)} />
|
|
42
|
+
<Property label="Private Cluster" value={getAzureMCPPrivateCluster(data) ? 'Yes' : 'No'} />
|
|
43
|
+
{spec.dnsServiceIP && <Property label="DNS Service IP" value={spec.dnsServiceIP} />}
|
|
44
|
+
{spec.loadBalancerSKU && <Property label="Load Balancer SKU" value={spec.loadBalancerSKU} />}
|
|
45
|
+
</PropertyList>
|
|
46
|
+
</Section>
|
|
47
|
+
|
|
48
|
+
{/* Upgrade */}
|
|
49
|
+
{spec.autoUpgradeProfile && (
|
|
50
|
+
<Section title="Upgrade" icon={Settings}>
|
|
51
|
+
<PropertyList>
|
|
52
|
+
<Property label="Channel" value={getAzureMCPUpgradeChannel(data)} />
|
|
53
|
+
</PropertyList>
|
|
54
|
+
</Section>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
{/* Authorized IP Ranges */}
|
|
58
|
+
{authorizedRanges.length > 0 && (
|
|
59
|
+
<Section title="Authorized IP Ranges" icon={Shield}>
|
|
60
|
+
<div className="flex flex-wrap gap-1">
|
|
61
|
+
{authorizedRanges.map((range: string, i: number) => (
|
|
62
|
+
<span key={i} className="badge badge-sm bg-theme-elevated text-theme-text-secondary border-theme-border font-mono text-[10px]">{range}</span>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</Section>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
<ConditionsSection conditions={conditions} />
|
|
69
|
+
</>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Server, Settings } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
import { getCAPIConditions } from '../resource-utils-capi'
|
|
5
|
+
import { getAzureMMPStatus, getAzureMMPSKU, getAzureMMPMode, getAzureMMPOSDiskInfo, getAzureMMPScaling, getAzureMMPScaleSetPriority, getAzureMMPOSType } from '../resource-utils-azure-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 AzureManagedMachinePoolRenderer({ data }: Props) {
|
|
13
|
+
const spec = data.spec || {}
|
|
14
|
+
const status = data.status || {}
|
|
15
|
+
const conditions = getCAPIConditions(data)
|
|
16
|
+
const mmpStatus = getAzureMMPStatus(data)
|
|
17
|
+
const isFailed = mmpStatus.level === 'unhealthy'
|
|
18
|
+
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
19
|
+
const scaling = getAzureMMPScaling(data)
|
|
20
|
+
const mode = getAzureMMPMode(data)
|
|
21
|
+
const priority = getAzureMMPScaleSetPriority(data)
|
|
22
|
+
const labels = spec.nodeLabels || {}
|
|
23
|
+
const taints = spec.taints || []
|
|
24
|
+
const zones = spec.availabilityZones || []
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
{isFailed && (
|
|
29
|
+
<AlertBanner variant="error" title="AKS Node Pool Not Ready" message={readyCond?.message || 'AzureManagedMachinePool is not ready.'} />
|
|
30
|
+
)}
|
|
31
|
+
|
|
32
|
+
<Section title="Overview" icon={Server}>
|
|
33
|
+
<PropertyList>
|
|
34
|
+
{spec.name && <Property label="Pool Name" value={spec.name} />}
|
|
35
|
+
<Property label="VM Size" value={getAzureMMPSKU(data)} />
|
|
36
|
+
<Property label="Mode" value={
|
|
37
|
+
<span className={clsx('badge badge-sm', mode === 'System'
|
|
38
|
+
? 'bg-purple-100 text-purple-800 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40'
|
|
39
|
+
: 'bg-sky-100 text-sky-700 border-sky-300 dark:bg-sky-950/50 dark:text-sky-400 dark:border-sky-700/40'
|
|
40
|
+
)}>{mode}</span>
|
|
41
|
+
} />
|
|
42
|
+
<Property label="OS" value={getAzureMMPOSType(data)} />
|
|
43
|
+
<Property label="OS Disk" value={getAzureMMPOSDiskInfo(data)} />
|
|
44
|
+
<Property label="Priority" value={
|
|
45
|
+
<span className={clsx('badge badge-sm', priority === 'Spot'
|
|
46
|
+
? 'bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950/50 dark:text-amber-400 dark:border-amber-700/40'
|
|
47
|
+
: 'bg-sky-100 text-sky-700 border-sky-300 dark:bg-sky-950/50 dark:text-sky-400 dark:border-sky-700/40'
|
|
48
|
+
)}>{priority}</span>
|
|
49
|
+
} />
|
|
50
|
+
{spec.maxPods != null && <Property label="Max Pods" value={String(spec.maxPods)} />}
|
|
51
|
+
</PropertyList>
|
|
52
|
+
</Section>
|
|
53
|
+
|
|
54
|
+
<Section title="Scaling" icon={Settings}>
|
|
55
|
+
<PropertyList>
|
|
56
|
+
<Property label="Min Size" value={String(scaling.min)} />
|
|
57
|
+
<Property label="Max Size" value={String(scaling.max)} />
|
|
58
|
+
<Property label="Current Replicas" value={String(status.replicas ?? 0)} />
|
|
59
|
+
{spec.scaleDownMode && <Property label="Scale Down Mode" value={spec.scaleDownMode} />}
|
|
60
|
+
</PropertyList>
|
|
61
|
+
</Section>
|
|
62
|
+
|
|
63
|
+
{/* Availability Zones */}
|
|
64
|
+
{zones.length > 0 && (
|
|
65
|
+
<Section title="Availability Zones" icon={Server}>
|
|
66
|
+
<div className="flex flex-wrap gap-1">
|
|
67
|
+
{zones.map((z: string) => (
|
|
68
|
+
<span key={z} className="badge badge-sm bg-theme-elevated text-theme-text-secondary border-theme-border">{z}</span>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
</Section>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
{/* Labels */}
|
|
75
|
+
{Object.keys(labels).length > 0 && (
|
|
76
|
+
<Section title="Node Labels" icon={Settings}>
|
|
77
|
+
<div className="flex flex-wrap gap-1">
|
|
78
|
+
{Object.entries(labels).map(([k, v]) => (
|
|
79
|
+
<span key={k} className="badge badge-sm bg-theme-elevated text-theme-text-secondary border-theme-border text-[10px]">{k}={v as string}</span>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
</Section>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{/* Taints */}
|
|
86
|
+
{taints.length > 0 && (
|
|
87
|
+
<Section title="Taints" icon={Settings}>
|
|
88
|
+
<table className="w-full text-xs">
|
|
89
|
+
<thead><tr className="text-theme-text-tertiary"><th className="text-left font-medium py-1">Key</th><th className="text-left font-medium py-1">Value</th><th className="text-left font-medium py-1">Effect</th></tr></thead>
|
|
90
|
+
<tbody>
|
|
91
|
+
{taints.map((t: any, i: number) => (
|
|
92
|
+
<tr key={i} className="border-t border-theme-border">
|
|
93
|
+
<td className="py-1 text-theme-text-secondary">{t.key}</td>
|
|
94
|
+
<td className="py-1 text-theme-text-secondary">{t.value}</td>
|
|
95
|
+
<td className="py-1 text-theme-text-secondary">{t.effect}</td>
|
|
96
|
+
</tr>
|
|
97
|
+
))}
|
|
98
|
+
</tbody>
|
|
99
|
+
</table>
|
|
100
|
+
</Section>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
<ConditionsSection conditions={conditions} />
|
|
104
|
+
</>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { BookOpen, Server, Settings, Shield } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
data: any
|
|
6
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function CAPIClusterClassRenderer({ data }: Props) {
|
|
10
|
+
const status = data.status || {}
|
|
11
|
+
const spec = data.spec || {}
|
|
12
|
+
const conditions = status.v1beta2?.conditions || status.conditions || []
|
|
13
|
+
|
|
14
|
+
const infraRef = spec.infrastructure?.ref || {}
|
|
15
|
+
const cpRef = spec.controlPlane?.ref || {}
|
|
16
|
+
const cpMachineInfraRef = spec.controlPlane?.machineInfrastructure?.ref || {}
|
|
17
|
+
const variables = spec.variables || []
|
|
18
|
+
const patches = spec.patches || []
|
|
19
|
+
const mdClasses = spec.workers?.machineDeployments || []
|
|
20
|
+
const mpClasses = spec.workers?.machinePools || []
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
{/* Overview */}
|
|
25
|
+
<Section title="Overview" icon={BookOpen}>
|
|
26
|
+
<PropertyList>
|
|
27
|
+
{infraRef.kind && (
|
|
28
|
+
<Property label="Infrastructure Template" value={`${infraRef.kind}/${infraRef.name}`} />
|
|
29
|
+
)}
|
|
30
|
+
{cpRef.kind && (
|
|
31
|
+
<Property label="Control Plane Template" value={`${cpRef.kind}/${cpRef.name}`} />
|
|
32
|
+
)}
|
|
33
|
+
{cpMachineInfraRef.kind && (
|
|
34
|
+
<Property label="CP Machine Infra" value={`${cpMachineInfraRef.kind}/${cpMachineInfraRef.name}`} />
|
|
35
|
+
)}
|
|
36
|
+
<Property label="Variables" value={String(variables.length)} />
|
|
37
|
+
<Property label="Patches" value={String(patches.length)} />
|
|
38
|
+
</PropertyList>
|
|
39
|
+
</Section>
|
|
40
|
+
|
|
41
|
+
{/* Control Plane */}
|
|
42
|
+
{cpRef.kind && (
|
|
43
|
+
<Section title="Control Plane" icon={Shield}>
|
|
44
|
+
<PropertyList>
|
|
45
|
+
<Property label="Template" value={`${cpRef.kind}/${cpRef.name}`} />
|
|
46
|
+
{spec.controlPlane?.machineHealthCheck && (
|
|
47
|
+
<Property label="Health Check" value="Configured" />
|
|
48
|
+
)}
|
|
49
|
+
</PropertyList>
|
|
50
|
+
</Section>
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
{/* Worker Topology */}
|
|
54
|
+
{(mdClasses.length > 0 || mpClasses.length > 0) && (
|
|
55
|
+
<Section title="Worker Topology" icon={Server}>
|
|
56
|
+
{mdClasses.length > 0 && (
|
|
57
|
+
<div className="mb-2">
|
|
58
|
+
<div className="text-xs font-medium text-theme-text-secondary mb-1">MachineDeployment Classes</div>
|
|
59
|
+
<table className="w-full text-xs">
|
|
60
|
+
<thead>
|
|
61
|
+
<tr className="text-theme-text-tertiary">
|
|
62
|
+
<th className="text-left font-medium py-1">Class</th>
|
|
63
|
+
<th className="text-left font-medium py-1">Infra Template</th>
|
|
64
|
+
<th className="text-left font-medium py-1">Bootstrap Template</th>
|
|
65
|
+
</tr>
|
|
66
|
+
</thead>
|
|
67
|
+
<tbody>
|
|
68
|
+
{mdClasses.map((md: any, i: number) => (
|
|
69
|
+
<tr key={i} className="border-t border-theme-border">
|
|
70
|
+
<td className="py-1 text-theme-text-secondary font-medium">{md.class}</td>
|
|
71
|
+
<td className="py-1 text-theme-text-secondary">{md.template?.infrastructure?.ref?.kind || '-'}</td>
|
|
72
|
+
<td className="py-1 text-theme-text-secondary">{md.template?.bootstrap?.ref?.kind || '-'}</td>
|
|
73
|
+
</tr>
|
|
74
|
+
))}
|
|
75
|
+
</tbody>
|
|
76
|
+
</table>
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
{mpClasses.length > 0 && (
|
|
80
|
+
<div>
|
|
81
|
+
<div className="text-xs font-medium text-theme-text-secondary mb-1">MachinePool Classes</div>
|
|
82
|
+
<table className="w-full text-xs">
|
|
83
|
+
<thead>
|
|
84
|
+
<tr className="text-theme-text-tertiary">
|
|
85
|
+
<th className="text-left font-medium py-1">Class</th>
|
|
86
|
+
<th className="text-left font-medium py-1">Infra Template</th>
|
|
87
|
+
<th className="text-left font-medium py-1">Bootstrap Template</th>
|
|
88
|
+
</tr>
|
|
89
|
+
</thead>
|
|
90
|
+
<tbody>
|
|
91
|
+
{mpClasses.map((mp: any, i: number) => (
|
|
92
|
+
<tr key={i} className="border-t border-theme-border">
|
|
93
|
+
<td className="py-1 text-theme-text-secondary font-medium">{mp.class}</td>
|
|
94
|
+
<td className="py-1 text-theme-text-secondary">{mp.template?.infrastructure?.ref?.kind || '-'}</td>
|
|
95
|
+
<td className="py-1 text-theme-text-secondary">{mp.template?.bootstrap?.ref?.kind || '-'}</td>
|
|
96
|
+
</tr>
|
|
97
|
+
))}
|
|
98
|
+
</tbody>
|
|
99
|
+
</table>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
</Section>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
{/* Variables */}
|
|
106
|
+
{variables.length > 0 && (
|
|
107
|
+
<Section title="Variables" icon={Settings}>
|
|
108
|
+
<table className="w-full text-xs">
|
|
109
|
+
<thead>
|
|
110
|
+
<tr className="text-theme-text-tertiary">
|
|
111
|
+
<th className="text-left font-medium py-1">Name</th>
|
|
112
|
+
<th className="text-left font-medium py-1">Required</th>
|
|
113
|
+
<th className="text-left font-medium py-1">Schema Type</th>
|
|
114
|
+
</tr>
|
|
115
|
+
</thead>
|
|
116
|
+
<tbody>
|
|
117
|
+
{variables.map((v: any, i: number) => (
|
|
118
|
+
<tr key={i} className="border-t border-theme-border">
|
|
119
|
+
<td className="py-1 text-theme-text-secondary font-medium">{v.name}</td>
|
|
120
|
+
<td className="py-1 text-theme-text-secondary">{v.required ? 'Yes' : 'No'}</td>
|
|
121
|
+
<td className="py-1 text-theme-text-secondary">{v.schema?.openAPIV3Schema?.type || '-'}</td>
|
|
122
|
+
</tr>
|
|
123
|
+
))}
|
|
124
|
+
</tbody>
|
|
125
|
+
</table>
|
|
126
|
+
</Section>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{/* Patches */}
|
|
130
|
+
{patches.length > 0 && (
|
|
131
|
+
<Section title="Patches" icon={Settings} defaultExpanded={false}>
|
|
132
|
+
<table className="w-full text-xs">
|
|
133
|
+
<thead>
|
|
134
|
+
<tr className="text-theme-text-tertiary">
|
|
135
|
+
<th className="text-left font-medium py-1">Name</th>
|
|
136
|
+
<th className="text-left font-medium py-1">Definitions</th>
|
|
137
|
+
<th className="text-left font-medium py-1">Enabled If</th>
|
|
138
|
+
</tr>
|
|
139
|
+
</thead>
|
|
140
|
+
<tbody>
|
|
141
|
+
{patches.map((p: any, i: number) => (
|
|
142
|
+
<tr key={i} className="border-t border-theme-border">
|
|
143
|
+
<td className="py-1 text-theme-text-secondary font-medium">{p.name}</td>
|
|
144
|
+
<td className="py-1 text-theme-text-secondary">{p.definitions?.length ?? 0}</td>
|
|
145
|
+
<td className="py-1 text-theme-text-secondary font-mono text-[10px]">{p.enabledIf || '-'}</td>
|
|
146
|
+
</tr>
|
|
147
|
+
))}
|
|
148
|
+
</tbody>
|
|
149
|
+
</table>
|
|
150
|
+
</Section>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
<ConditionsSection conditions={conditions} />
|
|
154
|
+
</>
|
|
155
|
+
)
|
|
156
|
+
}
|