@skyhook-io/k8s-ui 1.4.0 → 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.
Files changed (52) hide show
  1. package/package.json +2 -2
  2. package/src/components/resources/ResourcesView.tsx +112 -49
  3. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +190 -28
  4. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +22 -0
  5. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +156 -5
  6. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +28 -1
  7. package/src/components/resources/renderers/CertificateRenderer.tsx +47 -8
  8. package/src/components/resources/renderers/ChallengeRenderer.tsx +22 -3
  9. package/src/components/resources/renderers/CiliumNetworkPolicyRenderer.tsx +262 -45
  10. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +81 -9
  11. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +10 -3
  12. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +59 -1
  13. package/src/components/resources/renderers/ClusterNetworkPolicyRenderer.tsx +7 -21
  14. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +3 -5
  15. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +91 -3
  16. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -42
  17. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -41
  18. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +66 -2
  19. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +31 -1
  20. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +66 -1
  21. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +3 -0
  22. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +55 -1
  23. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +23 -0
  24. package/src/components/resources/renderers/KustomizationRenderer.tsx +38 -3
  25. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +7 -56
  26. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -42
  27. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +3 -8
  28. package/src/components/resources/renderers/PodMonitorRenderer.tsx +5 -7
  29. package/src/components/resources/renderers/PodRenderer.tsx +22 -74
  30. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +178 -20
  31. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +2 -2
  32. package/src/components/resources/renderers/RolloutRenderer.tsx +105 -3
  33. package/src/components/resources/renderers/SbomReportRenderer.tsx +46 -5
  34. package/src/components/resources/renderers/SealedSecretRenderer.tsx +11 -2
  35. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +5 -7
  36. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +11 -3
  37. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +11 -3
  38. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +8 -16
  39. package/src/components/resources/renderers/WorkflowRenderer.tsx +71 -17
  40. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +46 -1
  41. package/src/components/resources/renderers/karpenter-cells.tsx +18 -3
  42. package/src/components/resources/resource-utils-cnpg.ts +47 -0
  43. package/src/components/resources/resource-utils-karpenter.ts +6 -0
  44. package/src/components/resources/resource-utils-prometheus.ts +50 -6
  45. package/src/components/resources/resource-utils.ts +131 -33
  46. package/src/components/shared/ResourceRendererDispatch.tsx +4 -4
  47. package/src/components/timeline/TimelineList.tsx +11 -4
  48. package/src/components/ui/drawer-components.tsx +48 -1
  49. package/src/theme/variables.css +1 -1
  50. package/src/types/gitops.ts +8 -0
  51. package/src/utils/navigation.test.ts +29 -2
  52. package/src/utils/navigation.ts +45 -24
@@ -1,6 +1,6 @@
1
1
  import { Shield, ArrowDownToLine, ArrowUpFromLine } from 'lucide-react'
2
2
  import { clsx } from 'clsx'
3
- import { Section, PropertyList, Property } from '../../ui/drawer-components'
3
+ import { Section, PropertyList, Property, LabelSelectorDisplay } from '../../ui/drawer-components'
4
4
 
5
5
  interface ClusterNetworkPolicyRendererProps {
6
6
  data: any
@@ -24,7 +24,7 @@ export function ClusterNetworkPolicyRenderer({ data }: ClusterNetworkPolicyRende
24
24
  {subject.namespaces && (
25
25
  <div className="mt-2">
26
26
  <div className="text-xs text-theme-text-tertiary mb-1">Namespace Selector</div>
27
- <SelectorBadges selector={subject.namespaces} />
27
+ <LabelSelectorDisplay selector={subject.namespaces} />
28
28
  </div>
29
29
  )}
30
30
  {subject.pods && (
@@ -33,13 +33,13 @@ export function ClusterNetworkPolicyRenderer({ data }: ClusterNetworkPolicyRende
33
33
  {subject.pods.namespaceSelector && (
34
34
  <div className="mb-1">
35
35
  <span className="text-xs text-theme-text-secondary">namespaces: </span>
36
- <SelectorBadges selector={subject.pods.namespaceSelector} />
36
+ <LabelSelectorDisplay selector={subject.pods.namespaceSelector} inline />
37
37
  </div>
38
38
  )}
39
39
  {subject.pods.podSelector && (
40
40
  <div>
41
41
  <span className="text-xs text-theme-text-secondary">pods: </span>
42
- <SelectorBadges selector={subject.pods.podSelector} />
42
+ <LabelSelectorDisplay selector={subject.pods.podSelector} inline />
43
43
  </div>
44
44
  )}
45
45
  </div>
@@ -121,7 +121,7 @@ function AdminPeerEntry({ peer }: { peer: any }) {
121
121
  return (
122
122
  <div className="text-sm">
123
123
  <span className="text-theme-text-secondary text-xs">namespaces: </span>
124
- <SelectorBadges selector={peer.namespaces} />
124
+ <LabelSelectorDisplay selector={peer.namespaces} inline />
125
125
  </div>
126
126
  )
127
127
  }
@@ -131,13 +131,13 @@ function AdminPeerEntry({ peer }: { peer: any }) {
131
131
  {peer.pods.namespaceSelector && (
132
132
  <div>
133
133
  <span className="text-theme-text-secondary text-xs">namespaces: </span>
134
- <SelectorBadges selector={peer.pods.namespaceSelector} />
134
+ <LabelSelectorDisplay selector={peer.pods.namespaceSelector} inline />
135
135
  </div>
136
136
  )}
137
137
  {peer.pods.podSelector && (
138
138
  <div>
139
139
  <span className="text-theme-text-secondary text-xs">pods: </span>
140
- <SelectorBadges selector={peer.pods.podSelector} />
140
+ <LabelSelectorDisplay selector={peer.pods.podSelector} inline />
141
141
  </div>
142
142
  )}
143
143
  </div>
@@ -158,17 +158,3 @@ function AdminPeerEntry({ peer }: { peer: any }) {
158
158
  return null
159
159
  }
160
160
 
161
- function SelectorBadges({ selector }: { selector: any }) {
162
- const matchLabels = selector?.matchLabels || {}
163
- const hasLabels = Object.keys(matchLabels).length > 0
164
- if (!hasLabels) return <span className="text-xs text-theme-text-tertiary">all</span>
165
- return (
166
- <span className="inline-flex flex-wrap gap-1 align-middle">
167
- {Object.entries(matchLabels).map(([k, v]) => (
168
- <span key={k} className="badge bg-theme-elevated text-theme-text-secondary">
169
- {k}={String(v)}
170
- </span>
171
- ))}
172
- </span>
173
- )
174
- }
@@ -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 storeKind = store.kind === 'ClusterSecretStore'
80
- ? 'clustersecretstores'
81
- : 'secretstores'
79
+ const storeKindSingular = store.kind || 'SecretStore'
82
80
  return (
83
81
  <ResourceLink
84
82
  name={store.name}
85
- kind={storeKind}
86
- namespace={store.kind === 'ClusterSecretStore' ? '' : (data.metadata?.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}>
@@ -1,6 +1,5 @@
1
- import { GitBranch, AlertTriangle, FolderGit, CheckCircle2 } from 'lucide-react'
2
- import { clsx } from 'clsx'
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
- {/* Problem alerts */}
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, AlertTriangle, CheckCircle2, Shield } from 'lucide-react'
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
- {/* Problem alerts */}
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
- <Property label="Node Name" value={getNodeClaimNodeName(data)} />
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
@@ -1,4 +1,4 @@
1
- import { Server, Settings, Shield, Cpu, Tag } from 'lucide-react'
1
+ import { Server, Settings, Shield, Cpu, Tag, BarChart3 } from 'lucide-react'
2
2
  import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
3
  import { kindToPlural } from '../../../utils/navigation'
4
4
  import {
@@ -9,6 +9,16 @@ import {
9
9
  getNodePoolWeight,
10
10
  } from '../resource-utils-karpenter'
11
11
 
12
+ function formatCpuCores(value: string): string {
13
+ // Karpenter status.resources CPU is typically in millicores (e.g. "12000m") or cores (e.g. "12")
14
+ if (value.endsWith('m')) {
15
+ const millis = parseInt(value, 10)
16
+ if (!isNaN(millis)) return String(millis / 1000)
17
+ }
18
+ return value
19
+ }
20
+
21
+
12
22
  interface KarpenterNodePoolRendererProps {
13
23
  data: any
14
24
  onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
@@ -28,6 +38,9 @@ export function KarpenterNodePoolRenderer({ data, onNavigate }: KarpenterNodePoo
28
38
  const templateLabels = spec.template?.metadata?.labels || {}
29
39
  const templateExpireAfter = spec.template?.spec?.expireAfter
30
40
  const nodeClassRef = spec.template?.spec?.nodeClassRef
41
+ const templateTaints = spec.template?.spec?.taints || []
42
+ const templateStartupTaints = spec.template?.spec?.startupTaints || []
43
+ const statusResources = status.resources || {}
31
44
 
32
45
  return (
33
46
  <>
@@ -77,6 +90,26 @@ export function KarpenterNodePoolRenderer({ data, onNavigate }: KarpenterNodePoo
77
90
  </PropertyList>
78
91
  </Section>
79
92
 
93
+ {/* Resource Usage — from status.resources vs spec.limits */}
94
+ {(statusResources.cpu || statusResources.memory) && (
95
+ <Section title="Resource Usage" icon={BarChart3} defaultExpanded>
96
+ <PropertyList>
97
+ {statusResources.cpu && (
98
+ <Property
99
+ label="CPU"
100
+ value={`${formatCpuCores(statusResources.cpu)}${spec.limits?.cpu ? ` / ${formatCpuCores(spec.limits.cpu)}` : ''}`}
101
+ />
102
+ )}
103
+ {statusResources.memory && (
104
+ <Property
105
+ label="Memory"
106
+ value={`${statusResources.memory}${spec.limits?.memory ? ` / ${spec.limits.memory}` : ''}`}
107
+ />
108
+ )}
109
+ </PropertyList>
110
+ </Section>
111
+ )}
112
+
80
113
  {/* Disruption */}
81
114
  <Section title="Disruption" icon={Shield}>
82
115
  <PropertyList>
@@ -118,6 +151,38 @@ export function KarpenterNodePoolRenderer({ data, onNavigate }: KarpenterNodePoo
118
151
  </Section>
119
152
  )}
120
153
 
154
+ {/* Template Taints */}
155
+ {templateTaints.length > 0 && (
156
+ <Section title={`Template Taints (${templateTaints.length})`} icon={Shield} defaultExpanded>
157
+ <div className="flex flex-wrap gap-1">
158
+ {templateTaints.map((taint: any, i: number) => (
159
+ <span
160
+ key={i}
161
+ className="badge-sm bg-theme-hover text-theme-text-secondary"
162
+ >
163
+ {taint.key}={taint.value || ''}:{taint.effect || ''}
164
+ </span>
165
+ ))}
166
+ </div>
167
+ </Section>
168
+ )}
169
+
170
+ {/* Template Startup Taints */}
171
+ {templateStartupTaints.length > 0 && (
172
+ <Section title={`Startup Taints (${templateStartupTaints.length})`} icon={Shield} defaultExpanded>
173
+ <div className="flex flex-wrap gap-1">
174
+ {templateStartupTaints.map((taint: any, i: number) => (
175
+ <span
176
+ key={i}
177
+ className="badge-sm bg-theme-hover text-theme-text-secondary"
178
+ >
179
+ {taint.key}={taint.value || ''}:{taint.effect || ''}
180
+ </span>
181
+ ))}
182
+ </div>
183
+ </Section>
184
+ )}
185
+
121
186
  {/* Requirements */}
122
187
  {requirements.length > 0 && (
123
188
  <Section title={`Requirements (${requirements.length})`} icon={Settings} defaultExpanded>
@@ -46,6 +46,9 @@ export function KedaScaledJobRenderer({ data }: KedaScaledJobRendererProps) {
46
46
  {spec.failedJobsHistoryLimit !== undefined && (
47
47
  <Property label="Failure History" value={String(spec.failedJobsHistoryLimit)} />
48
48
  )}
49
+ {spec.minReplicaCount !== undefined && (
50
+ <Property label="Min Replicas" value={String(spec.minReplicaCount)} />
51
+ )}
49
52
  {spec.maxReplicaCount !== undefined && (
50
53
  <Property label="Max Replicas" value={String(spec.maxReplicaCount)} />
51
54
  )}
@@ -1,4 +1,4 @@
1
- import { Cpu } from 'lucide-react'
1
+ import { Cpu, Settings } from 'lucide-react'
2
2
  import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
3
  import { kindToPlural } from '../../../utils/navigation'
4
4
  import {
@@ -12,6 +12,16 @@ import {
12
12
  getScaledObjectCooldownPeriod,
13
13
  } from '../resource-utils-keda'
14
14
 
15
+ function summarizeScalingPolicies(policies: any[] | undefined, periodSeconds?: number): string {
16
+ if (!policies || policies.length === 0) return '-'
17
+ return policies.map((p: any) => {
18
+ const value = p.value ?? '?'
19
+ const type = p.type === 'Percent' ? '%' : ` pod${value !== 1 ? 's' : ''}`
20
+ const period = p.periodSeconds ?? periodSeconds ?? '?'
21
+ return `max ${value}${type}/${period}s`
22
+ }).join(', ')
23
+ }
24
+
15
25
  interface KedaScaledObjectRendererProps {
16
26
  data: any
17
27
  onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
@@ -131,6 +141,50 @@ export function KedaScaledObjectRenderer({ data, onNavigate }: KedaScaledObjectR
131
141
  </Section>
132
142
  )}
133
143
 
144
+ {/* Advanced section */}
145
+ {data.spec?.advanced && (
146
+ <Section title="Advanced" icon={Settings} defaultExpanded={false}>
147
+ <PropertyList>
148
+ {data.spec.advanced.restoreToOriginalReplicaCount != null && (
149
+ <Property
150
+ label="Restore Original Replicas"
151
+ value={data.spec.advanced.restoreToOriginalReplicaCount ? 'Yes' : 'No'}
152
+ />
153
+ )}
154
+ {data.spec.advanced.horizontalPodAutoscalerConfig?.behavior?.scaleUp && (
155
+ <Property
156
+ label="Scale Up"
157
+ value={summarizeScalingPolicies(
158
+ data.spec.advanced.horizontalPodAutoscalerConfig.behavior.scaleUp.policies,
159
+ data.spec.advanced.horizontalPodAutoscalerConfig.behavior.scaleUp.stabilizationWindowSeconds
160
+ )}
161
+ />
162
+ )}
163
+ {data.spec.advanced.horizontalPodAutoscalerConfig?.behavior?.scaleDown && (
164
+ <Property
165
+ label="Scale Down"
166
+ value={summarizeScalingPolicies(
167
+ data.spec.advanced.horizontalPodAutoscalerConfig.behavior.scaleDown.policies,
168
+ data.spec.advanced.horizontalPodAutoscalerConfig.behavior.scaleDown.stabilizationWindowSeconds
169
+ )}
170
+ />
171
+ )}
172
+ {data.spec.advanced.horizontalPodAutoscalerConfig?.behavior?.scaleUp?.stabilizationWindowSeconds != null && (
173
+ <Property
174
+ label="Scale Up Stabilization"
175
+ value={`${data.spec.advanced.horizontalPodAutoscalerConfig.behavior.scaleUp.stabilizationWindowSeconds}s`}
176
+ />
177
+ )}
178
+ {data.spec.advanced.horizontalPodAutoscalerConfig?.behavior?.scaleDown?.stabilizationWindowSeconds != null && (
179
+ <Property
180
+ label="Scale Down Stabilization"
181
+ value={`${data.spec.advanced.horizontalPodAutoscalerConfig.behavior.scaleDown.stabilizationWindowSeconds}s`}
182
+ />
183
+ )}
184
+ </PropertyList>
185
+ </Section>
186
+ )}
187
+
134
188
  <ConditionsSection conditions={conditions} />
135
189
  </>
136
190
  )