@skyhook-io/k8s-ui 1.3.2 → 1.4.1
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 +2 -2
- package/src/components/audit/AuditAlerts.tsx +88 -0
- package/src/components/audit/AuditCard.tsx +130 -0
- package/src/components/audit/AuditFindingsTable.tsx +526 -0
- package/src/components/audit/index.ts +3 -0
- package/src/components/dock/DockContext.tsx +5 -2
- package/src/components/dock/LocalTerminalTab.tsx +11 -0
- package/src/components/resources/ResourcesView.tsx +126 -49
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +190 -28
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +22 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +156 -5
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +28 -1
- package/src/components/resources/renderers/CertificateRenderer.tsx +47 -8
- package/src/components/resources/renderers/ChallengeRenderer.tsx +22 -3
- package/src/components/resources/renderers/CiliumNetworkPolicyRenderer.tsx +445 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +81 -9
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +10 -3
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +59 -1
- package/src/components/resources/renderers/ClusterNetworkPolicyRenderer.tsx +160 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +3 -5
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +91 -3
- package/src/components/resources/renderers/GenericRenderer.tsx +1 -1
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -42
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -41
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +66 -2
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +31 -1
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +66 -1
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +3 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +55 -1
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +23 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +38 -3
- package/src/components/resources/renderers/NetworkPolicyDiagram.tsx +277 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +17 -57
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -42
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +3 -8
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +5 -7
- package/src/components/resources/renderers/PodRenderer.tsx +22 -74
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +178 -20
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +2 -2
- package/src/components/resources/renderers/RolloutRenderer.tsx +105 -3
- package/src/components/resources/renderers/SbomReportRenderer.tsx +46 -5
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +11 -2
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +5 -7
- package/src/components/resources/renderers/ServiceRenderer.tsx +40 -28
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +11 -3
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +11 -3
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +8 -16
- package/src/components/resources/renderers/WorkflowRenderer.tsx +71 -17
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +46 -1
- package/src/components/resources/renderers/index.ts +3 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +18 -3
- package/src/components/resources/resource-utils-cnpg.ts +47 -0
- package/src/components/resources/resource-utils-karpenter.ts +6 -0
- package/src/components/resources/resource-utils-prometheus.ts +50 -6
- package/src/components/resources/resource-utils.ts +131 -33
- package/src/components/shared/CreateResourceDialog.tsx +236 -0
- package/src/components/shared/EditableYamlView.tsx +16 -1
- package/src/components/shared/ResourceActionsBar.tsx +9 -7
- package/src/components/shared/ResourceRendererDispatch.tsx +13 -6
- package/src/components/shared/index.ts +1 -0
- package/src/components/timeline/TimelineList.tsx +11 -4
- package/src/components/topology/K8sResourceNode.tsx +15 -0
- package/src/components/topology/TopologyControls.tsx +21 -1
- package/src/components/topology/TopologyGraph.tsx +1 -1
- package/src/components/ui/Badge.tsx +8 -0
- package/src/components/ui/drawer-components.tsx +68 -5
- package/src/components/workload/WorkloadView.tsx +55 -29
- package/src/index.ts +3 -0
- package/src/theme/variables.css +1 -1
- package/src/types/core.ts +2 -1
- package/src/types/gitops.ts +8 -0
- package/src/utils/badge-colors.ts +7 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/k8s-errors.ts +142 -0
- package/src/utils/navigation.test.ts +29 -2
- package/src/utils/navigation.ts +45 -24
- package/src/utils/resource-icons.ts +3 -0
- package/src/utils/skeleton-yaml.ts +302 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Shield, AlertTriangle, Globe, Lock, Key } from 'lucide-react'
|
|
1
|
+
import { Shield, AlertTriangle, Globe, Lock, Key, Server } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
|
|
4
4
|
|
|
@@ -7,6 +7,15 @@ function detectIssuerType(spec: any): string {
|
|
|
7
7
|
if (spec.ca) return 'CA'
|
|
8
8
|
if (spec.selfSigned !== undefined) return 'SelfSigned'
|
|
9
9
|
if (spec.vault) return 'Vault'
|
|
10
|
+
if (spec.venafi) return 'Venafi'
|
|
11
|
+
return 'Unknown'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getVaultAuthMethod(auth: any): string {
|
|
15
|
+
if (!auth) return 'Unknown'
|
|
16
|
+
if (auth.tokenSecretRef) return 'Token'
|
|
17
|
+
if (auth.appRole) return 'AppRole'
|
|
18
|
+
if (auth.kubernetes) return 'Kubernetes'
|
|
10
19
|
return 'Unknown'
|
|
11
20
|
}
|
|
12
21
|
|
|
@@ -138,6 +147,55 @@ function IssuerRendererBase({ data, kind }: { data: any; kind: string }) {
|
|
|
138
147
|
</Section>
|
|
139
148
|
)}
|
|
140
149
|
|
|
150
|
+
{/* Vault section */}
|
|
151
|
+
{spec.vault && (
|
|
152
|
+
<Section title="Vault Configuration" icon={Server}>
|
|
153
|
+
<PropertyList>
|
|
154
|
+
<Property label="Server" value={spec.vault.server} />
|
|
155
|
+
<Property label="Path" value={spec.vault.path} />
|
|
156
|
+
<Property label="Auth Method" value={getVaultAuthMethod(spec.vault.auth)} />
|
|
157
|
+
{spec.vault.auth?.appRole?.roleId && (
|
|
158
|
+
<Property label="AppRole Role ID" value={spec.vault.auth.appRole.roleId} />
|
|
159
|
+
)}
|
|
160
|
+
{spec.vault.auth?.appRole?.secretRef?.name && (
|
|
161
|
+
<Property label="AppRole Secret Ref" value={spec.vault.auth.appRole.secretRef.name} />
|
|
162
|
+
)}
|
|
163
|
+
{spec.vault.auth?.tokenSecretRef?.name && (
|
|
164
|
+
<Property label="Token Secret Ref" value={spec.vault.auth.tokenSecretRef.name} />
|
|
165
|
+
)}
|
|
166
|
+
{spec.vault.auth?.kubernetes?.role && (
|
|
167
|
+
<Property label="K8s Auth Role" value={spec.vault.auth.kubernetes.role} />
|
|
168
|
+
)}
|
|
169
|
+
{spec.vault.auth?.kubernetes?.secretRef?.name && (
|
|
170
|
+
<Property label="K8s Secret Ref" value={spec.vault.auth.kubernetes.secretRef.name} />
|
|
171
|
+
)}
|
|
172
|
+
<Property label="Custom CA" value={spec.vault.caBundle ? 'Yes' : 'No'} />
|
|
173
|
+
{spec.vault.namespace && (
|
|
174
|
+
<Property label="Vault Namespace" value={spec.vault.namespace} />
|
|
175
|
+
)}
|
|
176
|
+
</PropertyList>
|
|
177
|
+
</Section>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
{/* Venafi section */}
|
|
181
|
+
{spec.venafi && (
|
|
182
|
+
<Section title="Venafi Configuration" icon={Shield}>
|
|
183
|
+
<PropertyList>
|
|
184
|
+
<Property label="Zone" value={spec.venafi.zone} />
|
|
185
|
+
<Property
|
|
186
|
+
label="Platform"
|
|
187
|
+
value={spec.venafi.cloud ? 'Cloud' : spec.venafi.tpp ? 'TPP' : '-'}
|
|
188
|
+
/>
|
|
189
|
+
{spec.venafi.cloud?.url && (
|
|
190
|
+
<Property label="Cloud URL" value={spec.venafi.cloud.url} />
|
|
191
|
+
)}
|
|
192
|
+
{spec.venafi.tpp?.url && (
|
|
193
|
+
<Property label="TPP URL" value={spec.venafi.tpp.url} />
|
|
194
|
+
)}
|
|
195
|
+
</PropertyList>
|
|
196
|
+
</Section>
|
|
197
|
+
)}
|
|
198
|
+
|
|
141
199
|
{/* Conditions */}
|
|
142
200
|
<ConditionsSection conditions={conditions} />
|
|
143
201
|
</>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Shield, ArrowDownToLine, ArrowUpFromLine } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, LabelSelectorDisplay } from '../../ui/drawer-components'
|
|
4
|
+
|
|
5
|
+
interface ClusterNetworkPolicyRendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ClusterNetworkPolicyRenderer({ data }: ClusterNetworkPolicyRendererProps) {
|
|
10
|
+
const spec = data.spec || {}
|
|
11
|
+
const subject = spec.subject || {}
|
|
12
|
+
const priority = spec.priority
|
|
13
|
+
const ingress: any[] | undefined = spec.ingress
|
|
14
|
+
const egress: any[] | undefined = spec.egress
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<Section title="Subject" icon={Shield}>
|
|
19
|
+
<PropertyList>
|
|
20
|
+
{priority !== undefined && (
|
|
21
|
+
<Property label="Priority" value={String(priority)} />
|
|
22
|
+
)}
|
|
23
|
+
</PropertyList>
|
|
24
|
+
{subject.namespaces && (
|
|
25
|
+
<div className="mt-2">
|
|
26
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Namespace Selector</div>
|
|
27
|
+
<LabelSelectorDisplay selector={subject.namespaces} />
|
|
28
|
+
</div>
|
|
29
|
+
)}
|
|
30
|
+
{subject.pods && (
|
|
31
|
+
<div className="mt-2">
|
|
32
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Pod Selector</div>
|
|
33
|
+
{subject.pods.namespaceSelector && (
|
|
34
|
+
<div className="mb-1">
|
|
35
|
+
<span className="text-xs text-theme-text-secondary">namespaces: </span>
|
|
36
|
+
<LabelSelectorDisplay selector={subject.pods.namespaceSelector} inline />
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
{subject.pods.podSelector && (
|
|
40
|
+
<div>
|
|
41
|
+
<span className="text-xs text-theme-text-secondary">pods: </span>
|
|
42
|
+
<LabelSelectorDisplay selector={subject.pods.podSelector} inline />
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
</Section>
|
|
48
|
+
|
|
49
|
+
{ingress && ingress.length > 0 && (
|
|
50
|
+
<Section title={`Ingress Rules (${ingress.length})`} icon={ArrowDownToLine} defaultExpanded>
|
|
51
|
+
<div className="space-y-3">
|
|
52
|
+
{ingress.map((rule: any, i: number) => (
|
|
53
|
+
<AdminRuleCard key={i} rule={rule} direction="from" />
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</Section>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
{egress && egress.length > 0 && (
|
|
60
|
+
<Section title={`Egress Rules (${egress.length})`} icon={ArrowUpFromLine} defaultExpanded>
|
|
61
|
+
<div className="space-y-3">
|
|
62
|
+
{egress.map((rule: any, i: number) => (
|
|
63
|
+
<AdminRuleCard key={i} rule={rule} direction="to" />
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
</Section>
|
|
67
|
+
)}
|
|
68
|
+
</>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function AdminRuleCard({ rule, direction }: { rule: any; direction: 'from' | 'to' }) {
|
|
73
|
+
const action = rule.action || 'Allow'
|
|
74
|
+
const name = rule.name || ''
|
|
75
|
+
const peers: any[] = rule[direction] || []
|
|
76
|
+
const ports: any[] = rule.ports || []
|
|
77
|
+
|
|
78
|
+
const actionColor = action === 'Deny'
|
|
79
|
+
? 'bg-red-500/20 text-red-400 border-red-500/30'
|
|
80
|
+
: action === 'Pass'
|
|
81
|
+
? 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'
|
|
82
|
+
: 'bg-green-500/20 text-green-400 border-green-500/30'
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="card-inner-lg">
|
|
86
|
+
<div className="flex items-center gap-2 mb-2">
|
|
87
|
+
<span className={clsx('badge', actionColor)}>{action}</span>
|
|
88
|
+
{name && <span className="text-xs text-theme-text-secondary">{name}</span>}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{peers.length > 0 && (
|
|
92
|
+
<div className="mb-2">
|
|
93
|
+
<div className="text-xs text-theme-text-tertiary mb-1 capitalize">{direction}</div>
|
|
94
|
+
<div className="space-y-1.5">
|
|
95
|
+
{peers.map((peer: any, j: number) => (
|
|
96
|
+
<AdminPeerEntry key={j} peer={peer} />
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
{ports.length > 0 && (
|
|
103
|
+
<div>
|
|
104
|
+
<div className="text-xs text-theme-text-tertiary mb-1">Ports</div>
|
|
105
|
+
<div className="flex flex-wrap gap-1">
|
|
106
|
+
{ports.map((port: any, j: number) => (
|
|
107
|
+
<span key={j} className="badge bg-theme-elevated text-theme-text-secondary">
|
|
108
|
+
{port.portNumber?.protocol || 'TCP'}/{port.portNumber?.port || port.portRange?.start || '*'}
|
|
109
|
+
{port.portRange && `-${port.portRange.end}`}
|
|
110
|
+
</span>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function AdminPeerEntry({ peer }: { peer: any }) {
|
|
120
|
+
if (peer.namespaces) {
|
|
121
|
+
return (
|
|
122
|
+
<div className="text-sm">
|
|
123
|
+
<span className="text-theme-text-secondary text-xs">namespaces: </span>
|
|
124
|
+
<LabelSelectorDisplay selector={peer.namespaces} inline />
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
if (peer.pods) {
|
|
129
|
+
return (
|
|
130
|
+
<div className="text-sm space-y-0.5">
|
|
131
|
+
{peer.pods.namespaceSelector && (
|
|
132
|
+
<div>
|
|
133
|
+
<span className="text-theme-text-secondary text-xs">namespaces: </span>
|
|
134
|
+
<LabelSelectorDisplay selector={peer.pods.namespaceSelector} inline />
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
{peer.pods.podSelector && (
|
|
138
|
+
<div>
|
|
139
|
+
<span className="text-theme-text-secondary text-xs">pods: </span>
|
|
140
|
+
<LabelSelectorDisplay selector={peer.pods.podSelector} inline />
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
if (peer.networks) {
|
|
147
|
+
return (
|
|
148
|
+
<div className="text-sm">
|
|
149
|
+
<span className="text-theme-text-secondary text-xs">networks: </span>
|
|
150
|
+
<span className="inline-flex flex-wrap gap-1">
|
|
151
|
+
{peer.networks.map((n: string, i: number) => (
|
|
152
|
+
<span key={i} className="badge bg-theme-elevated text-theme-text-secondary">{n}</span>
|
|
153
|
+
))}
|
|
154
|
+
</span>
|
|
155
|
+
</div>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
@@ -76,14 +76,12 @@ export function ExternalSecretRenderer({ data, onNavigate }: ExternalSecretRende
|
|
|
76
76
|
<PropertyList>
|
|
77
77
|
<Property label="Store Name" value={(() => {
|
|
78
78
|
if (store.name && store.name !== '-') {
|
|
79
|
-
const
|
|
80
|
-
? 'clustersecretstores'
|
|
81
|
-
: 'secretstores'
|
|
79
|
+
const storeKindSingular = store.kind || 'SecretStore'
|
|
82
80
|
return (
|
|
83
81
|
<ResourceLink
|
|
84
82
|
name={store.name}
|
|
85
|
-
kind={
|
|
86
|
-
namespace={
|
|
83
|
+
kind={storeKindSingular}
|
|
84
|
+
namespace={storeKindSingular === 'ClusterSecretStore' ? '' : (data.metadata?.namespace || '')}
|
|
87
85
|
onNavigate={onNavigate}
|
|
88
86
|
/>
|
|
89
87
|
)
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { Package, Settings, CheckCircle2, History } from 'lucide-react'
|
|
1
|
+
import { Package, Settings, CheckCircle2, History, FileCode2, GitBranch } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
|
-
import { Section, PropertyList, Property, ConditionsSection, ProblemAlerts } from '../../ui/drawer-components'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, ProblemAlerts, ResourceLink } from '../../ui/drawer-components'
|
|
4
4
|
import { formatAge } from '../resource-utils'
|
|
5
5
|
import { GitOpsStatusBadge, SyncCountdown } from '../../gitops'
|
|
6
6
|
import { fluxConditionsToGitOpsStatus, type FluxCondition } from '../../../types/gitops'
|
|
7
7
|
|
|
8
8
|
interface FluxHelmReleaseRendererProps {
|
|
9
9
|
data: any
|
|
10
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export function FluxHelmReleaseRenderer({ data }: FluxHelmReleaseRendererProps) {
|
|
13
|
+
export function FluxHelmReleaseRenderer({ data, onNavigate }: FluxHelmReleaseRendererProps) {
|
|
13
14
|
const status = data.status || {}
|
|
14
15
|
const spec = data.spec || {}
|
|
15
16
|
const conditions = (status.conditions || []) as FluxCondition[]
|
|
@@ -40,6 +41,28 @@ export function FluxHelmReleaseRenderer({ data }: FluxHelmReleaseRendererProps)
|
|
|
40
41
|
})
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
// Revision mismatch detection — only flag when Ready=False to avoid false positives during active reconciliation
|
|
45
|
+
const isReadyFalse = conditions.find((c: any) => c.type === 'Ready')?.status === 'False'
|
|
46
|
+
if (
|
|
47
|
+
isReadyFalse &&
|
|
48
|
+
status.lastAppliedRevision &&
|
|
49
|
+
status.lastAttemptedRevision &&
|
|
50
|
+
status.lastAppliedRevision !== status.lastAttemptedRevision
|
|
51
|
+
) {
|
|
52
|
+
problems.push({
|
|
53
|
+
color: 'red',
|
|
54
|
+
message: `Revision mismatch: attempted ${status.lastAttemptedRevision} but applied ${status.lastAppliedRevision} — the latest reconciliation failed.`,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Values info
|
|
59
|
+
const valuesFrom = spec.valuesFrom || []
|
|
60
|
+
const values = spec.values || {}
|
|
61
|
+
const valueKeys = Object.keys(values)
|
|
62
|
+
|
|
63
|
+
// Dependencies
|
|
64
|
+
const dependsOn: Array<{ name: string; namespace?: string }> = spec.dependsOn || []
|
|
65
|
+
|
|
43
66
|
// Chart reference
|
|
44
67
|
const chartRef = spec.chart?.spec || {}
|
|
45
68
|
const sourceRef = chartRef.sourceRef || {}
|
|
@@ -97,6 +120,71 @@ export function FluxHelmReleaseRenderer({ data }: FluxHelmReleaseRendererProps)
|
|
|
97
120
|
</PropertyList>
|
|
98
121
|
</Section>
|
|
99
122
|
|
|
123
|
+
{/* Dependencies */}
|
|
124
|
+
{dependsOn.length > 0 && (
|
|
125
|
+
<Section title={`Dependencies (${dependsOn.length})`} icon={GitBranch}>
|
|
126
|
+
<div className="flex flex-wrap gap-1">
|
|
127
|
+
{dependsOn.map((dep, idx) => (
|
|
128
|
+
<ResourceLink
|
|
129
|
+
key={idx}
|
|
130
|
+
name={dep.name}
|
|
131
|
+
kind="helmreleases"
|
|
132
|
+
namespace={dep.namespace || data.metadata?.namespace || ''}
|
|
133
|
+
onNavigate={onNavigate}
|
|
134
|
+
/>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
</Section>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{/* Values */}
|
|
141
|
+
{(valuesFrom.length > 0 || valueKeys.length > 0) && (
|
|
142
|
+
<Section title="Values" icon={FileCode2} defaultExpanded={valuesFrom.length > 0}>
|
|
143
|
+
{valuesFrom.length > 0 && (
|
|
144
|
+
<div className="mb-3">
|
|
145
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1.5">
|
|
146
|
+
Values From
|
|
147
|
+
</div>
|
|
148
|
+
<div className="space-y-1">
|
|
149
|
+
{valuesFrom.map((ref: any, idx: number) => (
|
|
150
|
+
<div key={idx} className="card-inner text-sm flex items-center gap-2 flex-wrap">
|
|
151
|
+
<span className="badge-sm status-neutral">{ref.kind || 'ConfigMap'}</span>
|
|
152
|
+
<ResourceLink
|
|
153
|
+
name={ref.name}
|
|
154
|
+
kind={ref.kind === 'Secret' ? 'secrets' : 'configmaps'}
|
|
155
|
+
namespace={data.metadata?.namespace || ''}
|
|
156
|
+
onNavigate={onNavigate}
|
|
157
|
+
/>
|
|
158
|
+
{ref.valuesKey && (
|
|
159
|
+
<span className="text-theme-text-tertiary text-xs">
|
|
160
|
+
key: {ref.valuesKey}
|
|
161
|
+
</span>
|
|
162
|
+
)}
|
|
163
|
+
{ref.targetPath && (
|
|
164
|
+
<span className="text-theme-text-tertiary text-xs">
|
|
165
|
+
path: {ref.targetPath}
|
|
166
|
+
</span>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
{valueKeys.length > 0 && (
|
|
174
|
+
<div>
|
|
175
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1.5">
|
|
176
|
+
Inline Overrides ({valueKeys.length} {valueKeys.length === 1 ? 'key' : 'keys'})
|
|
177
|
+
</div>
|
|
178
|
+
<Section title={`${valueKeys.length} value ${valueKeys.length === 1 ? 'override' : 'overrides'}`} defaultExpanded={false}>
|
|
179
|
+
<pre className="text-xs text-theme-text-secondary font-mono bg-theme-elevated rounded-md p-2 overflow-x-auto max-h-48">
|
|
180
|
+
{JSON.stringify(values, null, 2)}
|
|
181
|
+
</pre>
|
|
182
|
+
</Section>
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
</Section>
|
|
186
|
+
)}
|
|
187
|
+
|
|
100
188
|
{/* Install/Upgrade settings */}
|
|
101
189
|
{(spec.install || spec.upgrade) && (
|
|
102
190
|
<Section title="Install/Upgrade Settings" defaultExpanded={false}>
|
|
@@ -358,7 +358,7 @@ function NestedObjectViewer({ name, value, depth = 0 }: NestedObjectViewerProps)
|
|
|
358
358
|
<div className="ml-5 mt-1 space-y-1">
|
|
359
359
|
{simpleEntries.map(([k, v]) => (
|
|
360
360
|
<div key={k} className="flex items-start gap-2 text-xs">
|
|
361
|
-
<span className="text-theme-text-tertiary w-
|
|
361
|
+
<span className="text-theme-text-tertiary w-40 shrink-0">{formatFieldName(k)}</span>
|
|
362
362
|
<span className="text-theme-text-secondary break-all">{formatValue(v)}</span>
|
|
363
363
|
</div>
|
|
364
364
|
))}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { GitBranch,
|
|
2
|
-
import {
|
|
3
|
-
import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
|
|
1
|
+
import { GitBranch, FolderGit, CheckCircle2 } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, ConditionsSection, ProblemAlerts } from '../../ui/drawer-components'
|
|
4
3
|
import { formatAge } from '../resource-utils'
|
|
5
4
|
import { formatBytes } from '../../../utils/format'
|
|
6
5
|
import { GitOpsStatusBadge, SyncCountdown } from '../../gitops'
|
|
@@ -37,45 +36,7 @@ export function GitRepositoryRenderer({ data }: GitRepositoryRendererProps) {
|
|
|
37
36
|
|
|
38
37
|
return (
|
|
39
38
|
<>
|
|
40
|
-
{
|
|
41
|
-
{problems.map((problem, i) => (
|
|
42
|
-
<div
|
|
43
|
-
key={i}
|
|
44
|
-
className={clsx(
|
|
45
|
-
'mb-4 p-3 border rounded-lg',
|
|
46
|
-
problem.color === 'red'
|
|
47
|
-
? 'bg-red-500/10 border-red-500/30'
|
|
48
|
-
: 'bg-yellow-500/10 border-yellow-500/30'
|
|
49
|
-
)}
|
|
50
|
-
>
|
|
51
|
-
<div className="flex items-start gap-2">
|
|
52
|
-
<AlertTriangle
|
|
53
|
-
className={clsx(
|
|
54
|
-
'w-4 h-4 mt-0.5 shrink-0',
|
|
55
|
-
problem.color === 'red' ? 'text-red-400' : 'text-yellow-400'
|
|
56
|
-
)}
|
|
57
|
-
/>
|
|
58
|
-
<div className="flex-1 min-w-0">
|
|
59
|
-
<div
|
|
60
|
-
className={clsx(
|
|
61
|
-
'text-sm font-medium',
|
|
62
|
-
problem.color === 'red' ? 'text-red-400' : 'text-yellow-400'
|
|
63
|
-
)}
|
|
64
|
-
>
|
|
65
|
-
{problem.color === 'red' ? 'Issue Detected' : 'Warning'}
|
|
66
|
-
</div>
|
|
67
|
-
<div
|
|
68
|
-
className={clsx(
|
|
69
|
-
'text-xs mt-1',
|
|
70
|
-
problem.color === 'red' ? 'text-red-300/80' : 'text-yellow-300/80'
|
|
71
|
-
)}
|
|
72
|
-
>
|
|
73
|
-
{problem.message}
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
))}
|
|
39
|
+
<ProblemAlerts problems={problems} />
|
|
79
40
|
|
|
80
41
|
{/* Status section */}
|
|
81
42
|
<Section title="Status">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Database,
|
|
1
|
+
import { Database, CheckCircle2, Shield } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
|
-
import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, ProblemAlerts } from '../../ui/drawer-components'
|
|
4
4
|
import { formatAge } from '../resource-utils'
|
|
5
5
|
import { formatBytes } from '../../../utils/format'
|
|
6
6
|
import { GitOpsStatusBadge, SyncCountdown } from '../../gitops'
|
|
@@ -35,45 +35,7 @@ export function HelmRepositoryRenderer({ data }: HelmRepositoryRendererProps) {
|
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<>
|
|
38
|
-
{
|
|
39
|
-
{problems.map((problem, i) => (
|
|
40
|
-
<div
|
|
41
|
-
key={i}
|
|
42
|
-
className={clsx(
|
|
43
|
-
'mb-4 p-3 border rounded-lg',
|
|
44
|
-
problem.color === 'red'
|
|
45
|
-
? 'bg-red-500/10 border-red-500/30'
|
|
46
|
-
: 'bg-yellow-500/10 border-yellow-500/30'
|
|
47
|
-
)}
|
|
48
|
-
>
|
|
49
|
-
<div className="flex items-start gap-2">
|
|
50
|
-
<AlertTriangle
|
|
51
|
-
className={clsx(
|
|
52
|
-
'w-4 h-4 mt-0.5 shrink-0',
|
|
53
|
-
problem.color === 'red' ? 'text-red-400' : 'text-yellow-400'
|
|
54
|
-
)}
|
|
55
|
-
/>
|
|
56
|
-
<div className="flex-1 min-w-0">
|
|
57
|
-
<div
|
|
58
|
-
className={clsx(
|
|
59
|
-
'text-sm font-medium',
|
|
60
|
-
problem.color === 'red' ? 'text-red-400' : 'text-yellow-400'
|
|
61
|
-
)}
|
|
62
|
-
>
|
|
63
|
-
{problem.color === 'red' ? 'Issue Detected' : 'Warning'}
|
|
64
|
-
</div>
|
|
65
|
-
<div
|
|
66
|
-
className={clsx(
|
|
67
|
-
'text-xs mt-1',
|
|
68
|
-
problem.color === 'red' ? 'text-red-300/80' : 'text-yellow-300/80'
|
|
69
|
-
)}
|
|
70
|
-
>
|
|
71
|
-
{problem.message}
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
))}
|
|
38
|
+
<ProblemAlerts problems={problems} />
|
|
77
39
|
|
|
78
40
|
{/* Status section */}
|
|
79
41
|
<Section title="Status">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Server, HardDrive, Shield, Network } from 'lucide-react'
|
|
2
|
-
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
1
|
+
import { Server, HardDrive, Shield, Network, Tag, Image } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, KeyValueBadgeList } from '../../ui/drawer-components'
|
|
3
3
|
import { getEC2NodeClassStatus } from '../resource-utils-karpenter'
|
|
4
4
|
|
|
5
5
|
interface KarpenterEC2NodeClassRendererProps {
|
|
@@ -20,6 +20,11 @@ export function KarpenterEC2NodeClassRenderer({ data }: KarpenterEC2NodeClassRen
|
|
|
20
20
|
const subnetTerms = spec.subnetSelectorTerms || []
|
|
21
21
|
const sgTerms = spec.securityGroupSelectorTerms || []
|
|
22
22
|
const metadataOptions = spec.metadataOptions
|
|
23
|
+
const statusAMIs = status.amis || []
|
|
24
|
+
const statusSubnets = status.subnets || []
|
|
25
|
+
const statusSecurityGroups = status.securityGroups || []
|
|
26
|
+
const specTags = spec.tags || {}
|
|
27
|
+
const instanceProfile = status.instanceProfile
|
|
23
28
|
|
|
24
29
|
return (
|
|
25
30
|
<>
|
|
@@ -42,6 +47,7 @@ export function KarpenterEC2NodeClassRenderer({ data }: KarpenterEC2NodeClassRen
|
|
|
42
47
|
/>
|
|
43
48
|
)}
|
|
44
49
|
{spec.amiFamily && <Property label="AMI Family" value={spec.amiFamily} />}
|
|
50
|
+
{instanceProfile && <Property label="Instance Profile" value={instanceProfile} />}
|
|
45
51
|
</PropertyList>
|
|
46
52
|
</Section>
|
|
47
53
|
|
|
@@ -151,6 +157,64 @@ export function KarpenterEC2NodeClassRenderer({ data }: KarpenterEC2NodeClassRen
|
|
|
151
157
|
</Section>
|
|
152
158
|
)}
|
|
153
159
|
|
|
160
|
+
{/* Resolved AMIs from status */}
|
|
161
|
+
{statusAMIs.length > 0 && (
|
|
162
|
+
<Section title={`Resolved AMIs (${statusAMIs.length})`} icon={Image} defaultExpanded>
|
|
163
|
+
<div className="space-y-2">
|
|
164
|
+
{statusAMIs.map((ami: any, i: number) => (
|
|
165
|
+
<div key={i} className="card-inner">
|
|
166
|
+
<div className="text-sm font-medium text-theme-text-primary mb-1">{ami.id || '(unknown AMI)'}</div>
|
|
167
|
+
{ami.name && (
|
|
168
|
+
<div className="text-xs text-theme-text-tertiary mb-1">{ami.name}</div>
|
|
169
|
+
)}
|
|
170
|
+
{ami.requirements && ami.requirements.length > 0 && (
|
|
171
|
+
<div className="flex flex-wrap gap-1">
|
|
172
|
+
{ami.requirements.map((req: any, ri: number) => (
|
|
173
|
+
<span key={ri} className="badge-sm bg-theme-hover text-theme-text-secondary">
|
|
174
|
+
{req.key}: {req.values?.join(', ') || req.operator}
|
|
175
|
+
</span>
|
|
176
|
+
))}
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
</Section>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{/* Resolved Subnets from status */}
|
|
186
|
+
{statusSubnets.length > 0 && (
|
|
187
|
+
<Section title={`Resolved Subnets (${statusSubnets.length})`} icon={Network} defaultExpanded>
|
|
188
|
+
<div className="flex flex-wrap gap-1">
|
|
189
|
+
{statusSubnets.map((subnet: any, i: number) => (
|
|
190
|
+
<span key={i} className="badge-sm bg-theme-hover text-theme-text-secondary">
|
|
191
|
+
{subnet.id}{subnet.zone ? ` (${subnet.zone})` : ''}
|
|
192
|
+
</span>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
</Section>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{/* Resolved Security Groups from status */}
|
|
199
|
+
{statusSecurityGroups.length > 0 && (
|
|
200
|
+
<Section title={`Resolved Security Groups (${statusSecurityGroups.length})`} icon={Shield} defaultExpanded>
|
|
201
|
+
<div className="flex flex-wrap gap-1">
|
|
202
|
+
{statusSecurityGroups.map((sg: any, i: number) => (
|
|
203
|
+
<span key={i} className="badge-sm bg-theme-hover text-theme-text-secondary">
|
|
204
|
+
{sg.id}{sg.name ? ` (${sg.name})` : ''}
|
|
205
|
+
</span>
|
|
206
|
+
))}
|
|
207
|
+
</div>
|
|
208
|
+
</Section>
|
|
209
|
+
)}
|
|
210
|
+
|
|
211
|
+
{/* EC2 Tags */}
|
|
212
|
+
{Object.keys(specTags).length > 0 && (
|
|
213
|
+
<Section title={`EC2 Tags (${Object.keys(specTags).length})`} icon={Tag} defaultExpanded>
|
|
214
|
+
<KeyValueBadgeList items={specTags} />
|
|
215
|
+
</Section>
|
|
216
|
+
)}
|
|
217
|
+
|
|
154
218
|
<ConditionsSection conditions={conditions} />
|
|
155
219
|
</>
|
|
156
220
|
)
|
|
@@ -3,6 +3,7 @@ import { clsx } from 'clsx'
|
|
|
3
3
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
4
4
|
import { kindToPlural } from '../../../utils/navigation'
|
|
5
5
|
import {
|
|
6
|
+
CAPACITY_TYPE_BADGE,
|
|
6
7
|
getNodeClaimStatus,
|
|
7
8
|
getNodeClaimInstanceType,
|
|
8
9
|
getNodeClaimNodeName,
|
|
@@ -35,6 +36,8 @@ export function KarpenterNodeClaimRenderer({ data, onNavigate }: KarpenterNodeCl
|
|
|
35
36
|
const status = data.status || {}
|
|
36
37
|
const conditions = status.conditions || []
|
|
37
38
|
|
|
39
|
+
const labels = data.metadata?.labels || {}
|
|
40
|
+
|
|
38
41
|
const claimStatus = getNodeClaimStatus(data)
|
|
39
42
|
const isNotReady = claimStatus.level === 'unhealthy'
|
|
40
43
|
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
@@ -42,6 +45,10 @@ export function KarpenterNodeClaimRenderer({ data, onNavigate }: KarpenterNodeCl
|
|
|
42
45
|
const requirements = getNodeClaimRequirements(data)
|
|
43
46
|
const nodeClassRef = getNodeClaimNodeClassRef(data)
|
|
44
47
|
const expireAfter = getNodeClaimExpireAfter(data)
|
|
48
|
+
const capacityType = labels['karpenter.sh/capacity-type'] || ''
|
|
49
|
+
const zone = labels['topology.kubernetes.io/zone'] || ''
|
|
50
|
+
const arch = labels['kubernetes.io/arch'] || ''
|
|
51
|
+
const nodeName = getNodeClaimNodeName(data)
|
|
45
52
|
|
|
46
53
|
// Provisioning steps for timeline
|
|
47
54
|
const steps = [
|
|
@@ -66,7 +73,30 @@ export function KarpenterNodeClaimRenderer({ data, onNavigate }: KarpenterNodeCl
|
|
|
66
73
|
<Section title="Instance" icon={Server}>
|
|
67
74
|
<PropertyList>
|
|
68
75
|
<Property label="Instance Type" value={getNodeClaimInstanceType(data)} />
|
|
69
|
-
|
|
76
|
+
{capacityType && (
|
|
77
|
+
<Property
|
|
78
|
+
label="Capacity Type"
|
|
79
|
+
value={
|
|
80
|
+
<span className={clsx('badge-sm', CAPACITY_TYPE_BADGE[capacityType] || '')}>
|
|
81
|
+
{capacityType}
|
|
82
|
+
</span>
|
|
83
|
+
}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
<Property
|
|
87
|
+
label="Node Name"
|
|
88
|
+
value={nodeName !== '-' ? (
|
|
89
|
+
<ResourceLink
|
|
90
|
+
name={nodeName}
|
|
91
|
+
kind="Node"
|
|
92
|
+
namespace=""
|
|
93
|
+
label={nodeName}
|
|
94
|
+
onNavigate={onNavigate}
|
|
95
|
+
/>
|
|
96
|
+
) : '-'}
|
|
97
|
+
/>
|
|
98
|
+
{zone && <Property label="Zone" value={zone} />}
|
|
99
|
+
{arch && <Property label="Architecture" value={arch} />}
|
|
70
100
|
<Property label="NodePool" value={getNodeClaimNodePoolRef(data)} />
|
|
71
101
|
{nodeClassRef && (
|
|
72
102
|
<Property
|