@skyhook-io/k8s-ui 1.0.0

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 (193) hide show
  1. package/package.json +63 -0
  2. package/src/components/dock/BottomDock.tsx +204 -0
  3. package/src/components/dock/DockContext.tsx +212 -0
  4. package/src/components/dock/TerminalTab.tsx +333 -0
  5. package/src/components/dock/index.ts +3 -0
  6. package/src/components/gitops/GitOpsActions.tsx +227 -0
  7. package/src/components/gitops/GitOpsStatusBadge.tsx +221 -0
  8. package/src/components/gitops/ManagedResourcesList.tsx +210 -0
  9. package/src/components/gitops/SyncCountdown.tsx +146 -0
  10. package/src/components/gitops/index.ts +4 -0
  11. package/src/components/logs/JsonLogLine.tsx +90 -0
  12. package/src/components/logs/LogCore.tsx +553 -0
  13. package/src/components/logs/LogToolbarSelects.tsx +74 -0
  14. package/src/components/logs/LogsViewer.tsx +146 -0
  15. package/src/components/logs/WorkloadLogsViewer.tsx +263 -0
  16. package/src/components/logs/index.ts +13 -0
  17. package/src/components/logs/useLogBuffer.ts +138 -0
  18. package/src/components/logs/useLogSearch.ts +151 -0
  19. package/src/components/logs/useLogStream.ts +61 -0
  20. package/src/components/resources/ResourcesView.tsx +5494 -0
  21. package/src/components/resources/index.ts +17 -0
  22. package/src/components/resources/renderers/AlertRenderer.tsx +166 -0
  23. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +233 -0
  24. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +89 -0
  25. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +194 -0
  26. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +79 -0
  27. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +68 -0
  28. package/src/components/resources/renderers/CertificateRenderer.tsx +209 -0
  29. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +123 -0
  30. package/src/components/resources/renderers/ChallengeRenderer.tsx +111 -0
  31. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +164 -0
  32. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +140 -0
  33. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +153 -0
  34. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +221 -0
  35. package/src/components/resources/renderers/ConfigMapRenderer.tsx +124 -0
  36. package/src/components/resources/renderers/CronJobRenderer.tsx +86 -0
  37. package/src/components/resources/renderers/EventRenderer.tsx +175 -0
  38. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +119 -0
  39. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +172 -0
  40. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +242 -0
  41. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +263 -0
  42. package/src/components/resources/renderers/GatewayClassRenderer.tsx +68 -0
  43. package/src/components/resources/renderers/GatewayRenderer.tsx +218 -0
  44. package/src/components/resources/renderers/GenericRenderer.tsx +476 -0
  45. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +154 -0
  46. package/src/components/resources/renderers/HPARenderer.tsx +164 -0
  47. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +303 -0
  48. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +167 -0
  49. package/src/components/resources/renderers/IngressClassRenderer.tsx +49 -0
  50. package/src/components/resources/renderers/IngressRenderer.tsx +104 -0
  51. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +175 -0
  52. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +176 -0
  53. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +125 -0
  54. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +84 -0
  55. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +112 -0
  56. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +288 -0
  57. package/src/components/resources/renderers/JobRenderer.tsx +103 -0
  58. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +157 -0
  59. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +188 -0
  60. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +148 -0
  61. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +90 -0
  62. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +136 -0
  63. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +151 -0
  64. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +70 -0
  65. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +309 -0
  66. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +142 -0
  67. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +232 -0
  68. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +97 -0
  69. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +74 -0
  70. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +138 -0
  71. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +220 -0
  72. package/src/components/resources/renderers/KustomizationRenderer.tsx +151 -0
  73. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +352 -0
  74. package/src/components/resources/renderers/LeaseRenderer.tsx +93 -0
  75. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +208 -0
  76. package/src/components/resources/renderers/NodeRenderer.tsx +282 -0
  77. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +153 -0
  78. package/src/components/resources/renderers/OrderRenderer.tsx +108 -0
  79. package/src/components/resources/renderers/PVCRenderer.tsx +98 -0
  80. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +138 -0
  81. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +126 -0
  82. package/src/components/resources/renderers/PodMonitorRenderer.tsx +63 -0
  83. package/src/components/resources/renderers/PodRenderer.tsx +677 -0
  84. package/src/components/resources/renderers/PriorityClassRenderer.tsx +50 -0
  85. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +62 -0
  86. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +71 -0
  87. package/src/components/resources/renderers/RoleBindingRenderer.tsx +97 -0
  88. package/src/components/resources/renderers/RoleRenderer.tsx +192 -0
  89. package/src/components/resources/renderers/RolloutRenderer.tsx +227 -0
  90. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +75 -0
  91. package/src/components/resources/renderers/SbomReportRenderer.tsx +98 -0
  92. package/src/components/resources/renderers/SealedSecretRenderer.tsx +121 -0
  93. package/src/components/resources/renderers/SecretRenderer.tsx +321 -0
  94. package/src/components/resources/renderers/SecretStoreRenderer.tsx +111 -0
  95. package/src/components/resources/renderers/ServiceAccountRenderer.tsx +81 -0
  96. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +65 -0
  97. package/src/components/resources/renderers/ServiceRenderer.tsx +98 -0
  98. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +195 -0
  99. package/src/components/resources/renderers/StorageClassRenderer.tsx +82 -0
  100. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +257 -0
  101. package/src/components/resources/renderers/VPARenderer.tsx +193 -0
  102. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +73 -0
  103. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +193 -0
  104. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +161 -0
  105. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +134 -0
  106. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +36 -0
  107. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +347 -0
  108. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +156 -0
  109. package/src/components/resources/renderers/WorkflowRenderer.tsx +202 -0
  110. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +91 -0
  111. package/src/components/resources/renderers/WorkloadRenderer.tsx +311 -0
  112. package/src/components/resources/renderers/argo-cells.tsx +118 -0
  113. package/src/components/resources/renderers/certmanager-cells.tsx +164 -0
  114. package/src/components/resources/renderers/cnpg-cells.tsx +161 -0
  115. package/src/components/resources/renderers/eso-cells.tsx +115 -0
  116. package/src/components/resources/renderers/flux-cells.tsx +253 -0
  117. package/src/components/resources/renderers/index.ts +105 -0
  118. package/src/components/resources/renderers/istio-cells.tsx +210 -0
  119. package/src/components/resources/renderers/karpenter-cells.tsx +107 -0
  120. package/src/components/resources/renderers/keda-cells.tsx +94 -0
  121. package/src/components/resources/renderers/knative-cells.tsx +396 -0
  122. package/src/components/resources/renderers/kyverno-cells.tsx +85 -0
  123. package/src/components/resources/renderers/prometheus-cells.tsx +74 -0
  124. package/src/components/resources/renderers/traefik-cells.tsx +120 -0
  125. package/src/components/resources/renderers/trivy-cells.tsx +153 -0
  126. package/src/components/resources/renderers/trivy-shared.tsx +67 -0
  127. package/src/components/resources/renderers/velero-cells.tsx +164 -0
  128. package/src/components/resources/resource-utils-argo.ts +155 -0
  129. package/src/components/resources/resource-utils-certmanager.ts +198 -0
  130. package/src/components/resources/resource-utils-cnpg.ts +344 -0
  131. package/src/components/resources/resource-utils-eso.ts +385 -0
  132. package/src/components/resources/resource-utils-flux.ts +186 -0
  133. package/src/components/resources/resource-utils-istio.ts +372 -0
  134. package/src/components/resources/resource-utils-karpenter.ts +150 -0
  135. package/src/components/resources/resource-utils-keda.ts +203 -0
  136. package/src/components/resources/resource-utils-knative.ts +218 -0
  137. package/src/components/resources/resource-utils-kyverno.ts +224 -0
  138. package/src/components/resources/resource-utils-prometheus.ts +159 -0
  139. package/src/components/resources/resource-utils-traefik.ts +180 -0
  140. package/src/components/resources/resource-utils-trivy.ts +146 -0
  141. package/src/components/resources/resource-utils-velero.ts +293 -0
  142. package/src/components/resources/resource-utils.ts +1558 -0
  143. package/src/components/shared/EditableYamlView.tsx +302 -0
  144. package/src/components/shared/ResourceActionsBar.tsx +924 -0
  145. package/src/components/shared/ResourceRendererDispatch.tsx +557 -0
  146. package/src/components/shared/index.ts +3 -0
  147. package/src/components/timeline/index.ts +1 -0
  148. package/src/components/timeline/shared.tsx +614 -0
  149. package/src/components/topology/GroupNode.tsx +198 -0
  150. package/src/components/topology/K8sResourceNode.tsx +440 -0
  151. package/src/components/topology/TopologyControls.tsx +62 -0
  152. package/src/components/topology/TopologyFilterSidebar.tsx +377 -0
  153. package/src/components/topology/TopologyGraph.tsx +748 -0
  154. package/src/components/topology/TopologySearch.tsx +304 -0
  155. package/src/components/topology/index.ts +7 -0
  156. package/src/components/topology/layout.ts +734 -0
  157. package/src/components/topology/layout.worker.ts +272 -0
  158. package/src/components/topology/topology.css +129 -0
  159. package/src/components/ui/CodeViewer.tsx +451 -0
  160. package/src/components/ui/ConfirmDialog.tsx +188 -0
  161. package/src/components/ui/ForceDeleteConfirmDialog.tsx +57 -0
  162. package/src/components/ui/HealthRing.tsx +72 -0
  163. package/src/components/ui/MetricsChart.tsx +259 -0
  164. package/src/components/ui/ResourceBar.tsx +68 -0
  165. package/src/components/ui/Toast.tsx +225 -0
  166. package/src/components/ui/Tooltip.tsx +165 -0
  167. package/src/components/ui/YamlEditor.tsx +280 -0
  168. package/src/components/ui/drawer-components.tsx +792 -0
  169. package/src/components/ui/index.ts +10 -0
  170. package/src/components/workload/ResourceDetailDrawer.tsx +158 -0
  171. package/src/components/workload/WorkloadView.tsx +1109 -0
  172. package/src/components/workload/index.ts +2 -0
  173. package/src/hooks/index.ts +9 -0
  174. package/src/hooks/useAnimatedUnmount.ts +30 -0
  175. package/src/hooks/useKeyboardShortcuts.tsx +336 -0
  176. package/src/hooks/useRefreshAnimation.ts +58 -0
  177. package/src/index.ts +35 -0
  178. package/src/types/core.ts +894 -0
  179. package/src/types/gitops.ts +458 -0
  180. package/src/types/index.ts +2 -0
  181. package/src/utils/animation.ts +40 -0
  182. package/src/utils/api-resources.ts +185 -0
  183. package/src/utils/badge-colors.ts +399 -0
  184. package/src/utils/context-name.ts +72 -0
  185. package/src/utils/download.ts +19 -0
  186. package/src/utils/format.ts +233 -0
  187. package/src/utils/index.ts +10 -0
  188. package/src/utils/log-format.ts +300 -0
  189. package/src/utils/navigation.ts +119 -0
  190. package/src/utils/resource-hierarchy.ts +630 -0
  191. package/src/utils/resource-icons.ts +206 -0
  192. package/src/vite-env.d.ts +1 -0
  193. package/tsconfig.json +24 -0
@@ -0,0 +1,352 @@
1
+ import { Shield, ShieldCheck, ShieldAlert, FileWarning, ListChecks, ChevronDown, ChevronRight } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { useState } from 'react'
4
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
5
+ import {
6
+ getPolicyReportSummary,
7
+ getPolicyReportResults,
8
+ getPolicyReportScope,
9
+ getPolicyReportSource,
10
+ getKyvernoPolicyAction,
11
+ getKyvernoPolicyRuleCount,
12
+ getKyvernoPolicyBackground,
13
+ getKyvernoPolicyRules,
14
+ getKyvernoPolicyRuleCountByType,
15
+ getKyvernoPolicyAutogenRules,
16
+ } from '../resource-utils-kyverno'
17
+
18
+ // ============================================================================
19
+ // PolicyReport / ClusterPolicyReport Renderer
20
+ // ============================================================================
21
+
22
+ interface PolicyReportRendererProps {
23
+ data: any
24
+ }
25
+
26
+ const resultColorMap: Record<string, string> = {
27
+ pass: 'bg-green-500/20 text-green-400',
28
+ fail: 'bg-red-500/20 text-red-400',
29
+ warn: 'bg-yellow-500/20 text-yellow-400',
30
+ error: 'bg-red-500/20 text-red-400',
31
+ skip: 'bg-blue-500/20 text-blue-400',
32
+ }
33
+
34
+ const severityColorMap: Record<string, string> = {
35
+ critical: 'bg-red-500/20 text-red-400',
36
+ high: 'bg-orange-500/20 text-orange-400',
37
+ medium: 'bg-yellow-500/20 text-yellow-400',
38
+ low: 'bg-blue-500/20 text-blue-400',
39
+ info: 'bg-theme-hover text-theme-text-tertiary',
40
+ }
41
+
42
+ function ResultRow({ result }: { result: any }) {
43
+ const [expanded, setExpanded] = useState(false)
44
+ const message = result.message || ''
45
+ const hasMessage = message.length > 0
46
+
47
+ return (
48
+ <div className="border-b border-theme-border/30 last:border-b-0">
49
+ <div
50
+ className={clsx(
51
+ 'flex items-center gap-2 py-1.5 px-2 text-xs',
52
+ hasMessage && 'cursor-pointer hover:bg-theme-hover/50',
53
+ )}
54
+ onClick={() => hasMessage && setExpanded(!expanded)}
55
+ >
56
+ {hasMessage ? (
57
+ expanded ? <ChevronDown className="w-3 h-3 text-theme-text-tertiary shrink-0" /> : <ChevronRight className="w-3 h-3 text-theme-text-tertiary shrink-0" />
58
+ ) : (
59
+ <span className="w-3 shrink-0" />
60
+ )}
61
+ {/* Result status */}
62
+ <span className={clsx('px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0', resultColorMap[result.result] || 'bg-theme-hover text-theme-text-tertiary')}>
63
+ {result.result || '-'}
64
+ </span>
65
+ {/* Severity */}
66
+ {result.severity && (
67
+ <span className={clsx('px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0', severityColorMap[result.severity?.toLowerCase()] || 'bg-theme-hover text-theme-text-tertiary')}>
68
+ {result.severity}
69
+ </span>
70
+ )}
71
+ {/* Policy / Rule */}
72
+ <span className="text-theme-text-secondary truncate">{result.policy || '-'}</span>
73
+ {result.rule && (
74
+ <span className="text-theme-text-tertiary truncate">/ {result.rule}</span>
75
+ )}
76
+ </div>
77
+ {expanded && message && (
78
+ <div className="px-2 pb-2 pl-7">
79
+ <div className="text-xs text-theme-text-secondary break-all bg-theme-elevated/30 rounded p-2">
80
+ {message}
81
+ </div>
82
+ {result.category && (
83
+ <div className="mt-1 text-[10px] text-theme-text-tertiary">Category: {result.category}</div>
84
+ )}
85
+ {result.source && (
86
+ <div className="text-[10px] text-theme-text-tertiary">Source: {result.source}</div>
87
+ )}
88
+ {result.resources && result.resources.length > 0 && (
89
+ <div className="mt-1 text-[10px] text-theme-text-tertiary">
90
+ Resources: {result.resources.map((r: any) => `${r.kind}/${r.namespace ? r.namespace + '/' : ''}${r.name}`).join(', ')}
91
+ </div>
92
+ )}
93
+ </div>
94
+ )}
95
+ </div>
96
+ )
97
+ }
98
+
99
+ export function PolicyReportRenderer({ data }: PolicyReportRendererProps) {
100
+ const summary = getPolicyReportSummary(data)
101
+ const results = getPolicyReportResults(data)
102
+ const scope = getPolicyReportScope(data)
103
+ const source = getPolicyReportSource(data)
104
+ const total = summary.pass + summary.fail + summary.warn + summary.error + summary.skip
105
+
106
+ const failResults = results.filter((r: any) => r.result === 'fail')
107
+ const errorResults = results.filter((r: any) => r.result === 'error')
108
+
109
+ return (
110
+ <>
111
+ {/* Problem alerts */}
112
+ {summary.fail > 0 && (
113
+ <AlertBanner
114
+ variant="error"
115
+ title={`${summary.fail} policy check${summary.fail > 1 ? 's' : ''} failed`}
116
+ message={failResults.length <= 3
117
+ ? failResults.map((r: any) => `${r.policy}${r.rule ? '/' + r.rule : ''}: ${r.message || 'failed'}`).join('; ')
118
+ : `${failResults.slice(0, 2).map((r: any) => r.policy).join(', ')} and ${failResults.length - 2} more`
119
+ }
120
+ />
121
+ )}
122
+ {summary.error > 0 && summary.fail === 0 && (
123
+ <AlertBanner
124
+ variant="error"
125
+ title={`${summary.error} policy check${summary.error > 1 ? 's' : ''} errored`}
126
+ message={errorResults.length <= 3
127
+ ? errorResults.map((r: any) => `${r.policy}: ${r.message || 'error'}`).join('; ')
128
+ : undefined
129
+ }
130
+ />
131
+ )}
132
+ {summary.warn > 0 && summary.fail === 0 && summary.error === 0 && (
133
+ <AlertBanner
134
+ variant="warning"
135
+ title={`${summary.warn} policy warning${summary.warn > 1 ? 's' : ''}`}
136
+ />
137
+ )}
138
+
139
+ {/* Summary */}
140
+ <Section title="Summary" icon={ShieldCheck}>
141
+ <PropertyList>
142
+ {scope !== '-' && <Property label="Scope" value={scope} />}
143
+ {source !== '-' && <Property label="Source" value={source} />}
144
+ </PropertyList>
145
+
146
+ {/* Visual summary bar */}
147
+ {total > 0 && (
148
+ <div className="mt-3">
149
+ <div className="flex rounded overflow-hidden h-5 w-full">
150
+ {summary.pass > 0 && (
151
+ <div
152
+ className="bg-green-500/60 flex items-center justify-center text-[10px] font-medium text-white"
153
+ style={{ width: `${(summary.pass / total) * 100}%` }}
154
+ title={`Pass: ${summary.pass}`}
155
+ >
156
+ {summary.pass > 0 && total > 3 ? summary.pass : ''}
157
+ </div>
158
+ )}
159
+ {summary.fail > 0 && (
160
+ <div
161
+ className="bg-red-500/60 flex items-center justify-center text-[10px] font-medium text-white"
162
+ style={{ width: `${(summary.fail / total) * 100}%` }}
163
+ title={`Fail: ${summary.fail}`}
164
+ >
165
+ {summary.fail > 0 && total > 3 ? summary.fail : ''}
166
+ </div>
167
+ )}
168
+ {summary.warn > 0 && (
169
+ <div
170
+ className="bg-yellow-500/60 flex items-center justify-center text-[10px] font-medium text-white"
171
+ style={{ width: `${(summary.warn / total) * 100}%` }}
172
+ title={`Warn: ${summary.warn}`}
173
+ >
174
+ {summary.warn > 0 && total > 3 ? summary.warn : ''}
175
+ </div>
176
+ )}
177
+ {summary.error > 0 && (
178
+ <div
179
+ className="bg-red-400/60 flex items-center justify-center text-[10px] font-medium text-white"
180
+ style={{ width: `${(summary.error / total) * 100}%` }}
181
+ title={`Error: ${summary.error}`}
182
+ >
183
+ {summary.error > 0 && total > 3 ? summary.error : ''}
184
+ </div>
185
+ )}
186
+ {summary.skip > 0 && (
187
+ <div
188
+ className="bg-blue-500/40 flex items-center justify-center text-[10px] font-medium text-white"
189
+ style={{ width: `${(summary.skip / total) * 100}%` }}
190
+ title={`Skip: ${summary.skip}`}
191
+ >
192
+ {summary.skip > 0 && total > 3 ? summary.skip : ''}
193
+ </div>
194
+ )}
195
+ </div>
196
+ <div className="flex gap-3 mt-1.5 text-[10px]">
197
+ {summary.pass > 0 && <span className="text-green-400">Pass: {summary.pass}</span>}
198
+ {summary.fail > 0 && <span className="text-red-400">Fail: {summary.fail}</span>}
199
+ {summary.warn > 0 && <span className="text-yellow-400">Warn: {summary.warn}</span>}
200
+ {summary.error > 0 && <span className="text-red-400">Error: {summary.error}</span>}
201
+ {summary.skip > 0 && <span className="text-blue-400">Skip: {summary.skip}</span>}
202
+ </div>
203
+ </div>
204
+ )}
205
+ </Section>
206
+
207
+ {/* Results */}
208
+ {results.length > 0 && (
209
+ <Section title={`Results (${results.length})`} icon={ListChecks} defaultExpanded>
210
+ <div className="bg-theme-elevated/20 rounded-lg overflow-hidden max-h-[500px] overflow-y-auto">
211
+ {results.map((result: any, i: number) => (
212
+ <ResultRow key={i} result={result} />
213
+ ))}
214
+ </div>
215
+ </Section>
216
+ )}
217
+ </>
218
+ )
219
+ }
220
+
221
+ // ============================================================================
222
+ // Kyverno Policy / ClusterPolicy Renderer
223
+ // ============================================================================
224
+
225
+ interface KyvernoPolicyRendererProps {
226
+ data: any
227
+ }
228
+
229
+ const ruleTypeColorMap: Record<string, string> = {
230
+ validate: 'bg-blue-500/20 text-blue-400',
231
+ mutate: 'bg-purple-500/20 text-purple-400',
232
+ generate: 'bg-green-500/20 text-green-400',
233
+ verifyImages: 'bg-orange-500/20 text-orange-400',
234
+ }
235
+
236
+ export function KyvernoPolicyRenderer({ data }: KyvernoPolicyRendererProps) {
237
+ const spec = data.spec || {}
238
+ const status = data.status || {}
239
+ const conditions = status.conditions || []
240
+ const action = getKyvernoPolicyAction(data)
241
+ const ruleCount = getKyvernoPolicyRuleCount(data)
242
+ const background = getKyvernoPolicyBackground(data)
243
+ const rules = getKyvernoPolicyRules(data)
244
+ const ruleCountByType = getKyvernoPolicyRuleCountByType(data)
245
+ const autogenRules = getKyvernoPolicyAutogenRules(data)
246
+
247
+ const isEnforce = action === 'Enforce'
248
+
249
+ return (
250
+ <>
251
+ {/* Configuration */}
252
+ <Section title="Configuration" icon={Shield}>
253
+ <PropertyList>
254
+ <Property label="Failure Action" value={
255
+ <span className={clsx(
256
+ 'px-2 py-0.5 rounded text-xs font-medium',
257
+ isEnforce ? 'bg-red-500/20 text-red-400' : 'bg-yellow-500/20 text-yellow-400',
258
+ )}>
259
+ {action}
260
+ </span>
261
+ } />
262
+ <Property label="Background" value={background ? 'Enabled' : 'Disabled'} />
263
+ {spec.webhookTimeoutSeconds && (
264
+ <Property label="Webhook Timeout" value={`${spec.webhookTimeoutSeconds}s`} />
265
+ )}
266
+ {spec.failurePolicy && (
267
+ <Property label="Failure Policy" value={spec.failurePolicy} />
268
+ )}
269
+ {spec.schemaValidation !== undefined && (
270
+ <Property label="Schema Validation" value={spec.schemaValidation ? 'Enabled' : 'Disabled'} />
271
+ )}
272
+ </PropertyList>
273
+
274
+ {/* Rule count summary */}
275
+ <div className="mt-3 flex flex-wrap gap-2">
276
+ {ruleCountByType.validate > 0 && (
277
+ <span className="px-2 py-0.5 rounded text-[10px] font-medium bg-blue-500/20 text-blue-400">
278
+ {ruleCountByType.validate} validate
279
+ </span>
280
+ )}
281
+ {ruleCountByType.mutate > 0 && (
282
+ <span className="px-2 py-0.5 rounded text-[10px] font-medium bg-purple-500/20 text-purple-400">
283
+ {ruleCountByType.mutate} mutate
284
+ </span>
285
+ )}
286
+ {ruleCountByType.generate > 0 && (
287
+ <span className="px-2 py-0.5 rounded text-[10px] font-medium bg-green-500/20 text-green-400">
288
+ {ruleCountByType.generate} generate
289
+ </span>
290
+ )}
291
+ {ruleCountByType.verifyImages > 0 && (
292
+ <span className="px-2 py-0.5 rounded text-[10px] font-medium bg-orange-500/20 text-orange-400">
293
+ {ruleCountByType.verifyImages} verifyImages
294
+ </span>
295
+ )}
296
+ </div>
297
+ </Section>
298
+
299
+ {/* Rules */}
300
+ {ruleCount > 0 && (
301
+ <Section title={`Rules (${ruleCount})`} icon={FileWarning} defaultExpanded>
302
+ <div className="space-y-2">
303
+ {rules.map((rule, i) => (
304
+ <div key={i} className="bg-theme-elevated/30 rounded-lg p-3">
305
+ <div className="flex items-center gap-2 mb-1">
306
+ <span className="text-sm font-medium text-theme-text-primary">{rule.name}</span>
307
+ <span className={clsx('px-1.5 py-0.5 rounded text-[10px] font-medium', ruleTypeColorMap[rule.type] || 'bg-theme-hover text-theme-text-tertiary')}>
308
+ {rule.type}
309
+ </span>
310
+ </div>
311
+ <div className="flex gap-3 text-[10px] text-theme-text-tertiary">
312
+ {rule.hasMatch && <span>match</span>}
313
+ {rule.hasExclude && <span>exclude</span>}
314
+ </div>
315
+ </div>
316
+ ))}
317
+ </div>
318
+ </Section>
319
+ )}
320
+
321
+ {/* Autogenerated Rules */}
322
+ {autogenRules.length > 0 && (
323
+ <Section title={`Autogenerated Rules (${autogenRules.length})`} defaultExpanded={false}>
324
+ <div className="flex flex-wrap gap-1">
325
+ {autogenRules.map((name: string) => (
326
+ <span
327
+ key={name}
328
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
329
+ >
330
+ {name}
331
+ </span>
332
+ ))}
333
+ </div>
334
+ </Section>
335
+ )}
336
+
337
+ {/* Status rulecount from Kyverno controller */}
338
+ {status.rulecount && (
339
+ <Section title="Status Rule Count" icon={ShieldAlert} defaultExpanded={false}>
340
+ <PropertyList>
341
+ {status.rulecount.validate !== undefined && <Property label="Validate" value={status.rulecount.validate} />}
342
+ {status.rulecount.mutate !== undefined && <Property label="Mutate" value={status.rulecount.mutate} />}
343
+ {status.rulecount.generate !== undefined && <Property label="Generate" value={status.rulecount.generate} />}
344
+ {status.rulecount.verifyImages !== undefined && <Property label="Verify Images" value={status.rulecount.verifyImages} />}
345
+ </PropertyList>
346
+ </Section>
347
+ )}
348
+
349
+ <ConditionsSection conditions={conditions} />
350
+ </>
351
+ )
352
+ }
@@ -0,0 +1,93 @@
1
+ import { Timer } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, AlertBanner } from '../../ui/drawer-components'
4
+
5
+ interface LeaseRendererProps {
6
+ data: any
7
+ }
8
+
9
+ function formatRelativeTime(timestamp: string): string {
10
+ const diff = Date.now() - new Date(timestamp).getTime()
11
+ if (diff < 0) return 'just now'
12
+ const seconds = Math.floor(diff / 1000)
13
+ if (seconds < 60) return `${seconds}s ago`
14
+ const minutes = Math.floor(seconds / 60)
15
+ if (minutes < 60) return `${minutes}m ago`
16
+ const hours = Math.floor(minutes / 60)
17
+ if (hours < 24) return `${hours}h ago`
18
+ const days = Math.floor(hours / 24)
19
+ return `${days}d ago`
20
+ }
21
+
22
+ export function LeaseRenderer({ data }: LeaseRendererProps) {
23
+ const spec = data.spec || {}
24
+
25
+ const renewTime: string | undefined = spec.renewTime
26
+ const leaseDurationSeconds: number | undefined = spec.leaseDurationSeconds
27
+
28
+ // Staleness calculation
29
+ let isStale = false
30
+ let ageRatio = 0
31
+ if (renewTime && leaseDurationSeconds) {
32
+ const elapsedSeconds = (Date.now() - new Date(renewTime).getTime()) / 1000
33
+ ageRatio = elapsedSeconds / leaseDurationSeconds
34
+ isStale = elapsedSeconds > leaseDurationSeconds
35
+ }
36
+
37
+ const renewColor = !renewTime
38
+ ? ''
39
+ : isStale
40
+ ? 'text-red-400'
41
+ : ageRatio > 0.75
42
+ ? 'text-amber-400'
43
+ : 'text-green-400'
44
+
45
+ return (
46
+ <>
47
+ {isStale && (
48
+ <AlertBanner
49
+ variant="warning"
50
+ title="Lease May Be Expired"
51
+ message="The lease has not been renewed within its duration period. The holder may be unhealthy."
52
+ />
53
+ )}
54
+
55
+ <Section title="Lease" icon={Timer} defaultExpanded>
56
+ <PropertyList>
57
+ <Property label="Holder" value={spec.holderIdentity || '-'} />
58
+ <Property
59
+ label="Duration"
60
+ value={leaseDurationSeconds != null ? `${leaseDurationSeconds}s` : '-'}
61
+ />
62
+ <Property
63
+ label="Transitions"
64
+ value={spec.leaseTransitions != null ? String(spec.leaseTransitions) : '0'}
65
+ />
66
+ <Property
67
+ label="Acquired"
68
+ value={
69
+ spec.acquireTime ? (
70
+ <span title={new Date(spec.acquireTime).toLocaleString()}>
71
+ {formatRelativeTime(spec.acquireTime)}
72
+ </span>
73
+ ) : undefined
74
+ }
75
+ />
76
+ <Property
77
+ label="Last Renewed"
78
+ value={
79
+ renewTime ? (
80
+ <span
81
+ className={clsx(renewColor)}
82
+ title={new Date(renewTime).toLocaleString()}
83
+ >
84
+ {formatRelativeTime(renewTime)}
85
+ </span>
86
+ ) : undefined
87
+ }
88
+ />
89
+ </PropertyList>
90
+ </Section>
91
+ </>
92
+ )
93
+ }
@@ -0,0 +1,208 @@
1
+ import { Shield, ArrowDownToLine, ArrowUpFromLine } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property } from '../../ui/drawer-components'
4
+
5
+ interface NetworkPolicyRendererProps {
6
+ data: any
7
+ }
8
+
9
+ export function NetworkPolicyRenderer({ data }: NetworkPolicyRendererProps) {
10
+ const spec = data.spec || {}
11
+ const podSelector = spec.podSelector || {}
12
+ const policyTypes: string[] = spec.policyTypes || []
13
+ const ingress: any[] | undefined = spec.ingress
14
+ const egress: any[] | undefined = spec.egress
15
+
16
+ const matchLabels = podSelector.matchLabels || {}
17
+ const hasMatchLabels = Object.keys(matchLabels).length > 0
18
+ const hasIngress = policyTypes.includes('Ingress')
19
+ const hasEgress = policyTypes.includes('Egress')
20
+
21
+ return (
22
+ <>
23
+ <Section title="Target" icon={Shield}>
24
+ <PropertyList>
25
+ <Property
26
+ label="Pod Selector"
27
+ value={hasMatchLabels ? undefined : 'All pods in namespace'}
28
+ />
29
+ </PropertyList>
30
+ {hasMatchLabels && (
31
+ <div className="mt-2">
32
+ <div className="text-xs text-theme-text-tertiary mb-1">Pod Selector</div>
33
+ <div className="flex flex-wrap gap-1">
34
+ {Object.entries(matchLabels).map(([k, v]) => (
35
+ <span
36
+ key={k}
37
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
38
+ >
39
+ {k}={String(v)}
40
+ </span>
41
+ ))}
42
+ </div>
43
+ </div>
44
+ )}
45
+ {policyTypes.length > 0 && (
46
+ <div className="mt-2">
47
+ <div className="text-xs text-theme-text-tertiary mb-1">Policy Types</div>
48
+ <div className="flex flex-wrap gap-1">
49
+ {policyTypes.map((type) => (
50
+ <span
51
+ key={type}
52
+ className={clsx(
53
+ 'px-2 py-0.5 rounded text-xs border',
54
+ type === 'Ingress'
55
+ ? 'bg-blue-500/20 text-blue-400 border-blue-500/30'
56
+ : 'bg-purple-500/20 text-purple-400 border-purple-500/30'
57
+ )}
58
+ >
59
+ {type}
60
+ </span>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ )}
65
+ </Section>
66
+
67
+ {hasIngress && (
68
+ <Section title="Ingress Rules" icon={ArrowDownToLine} defaultExpanded>
69
+ {ingress && ingress.length > 0 ? (
70
+ <div className="space-y-3">
71
+ {ingress.map((rule: any, i: number) => (
72
+ <IngressEgressRuleCard key={i} rule={rule} direction="from" />
73
+ ))}
74
+ </div>
75
+ ) : (
76
+ <div className="text-sm text-red-400">Deny all ingress</div>
77
+ )}
78
+ </Section>
79
+ )}
80
+
81
+ {hasEgress && (
82
+ <Section title="Egress Rules" icon={ArrowUpFromLine} defaultExpanded>
83
+ {egress && egress.length > 0 ? (
84
+ <div className="space-y-3">
85
+ {egress.map((rule: any, i: number) => (
86
+ <IngressEgressRuleCard key={i} rule={rule} direction="to" />
87
+ ))}
88
+ </div>
89
+ ) : (
90
+ <div className="text-sm text-red-400">Deny all egress</div>
91
+ )}
92
+ </Section>
93
+ )}
94
+ </>
95
+ )
96
+ }
97
+
98
+ function IngressEgressRuleCard({
99
+ rule,
100
+ direction,
101
+ }: {
102
+ rule: any
103
+ direction: 'from' | 'to'
104
+ }) {
105
+ const peers: any[] = rule[direction] || []
106
+ const ports: any[] = rule.ports || []
107
+
108
+ return (
109
+ <div className="bg-theme-elevated/30 rounded p-3">
110
+ {peers.length > 0 && (
111
+ <div className="mb-2">
112
+ <div className="text-xs text-theme-text-tertiary mb-1 capitalize">{direction}</div>
113
+ <div className="space-y-1.5">
114
+ {peers.map((peer: any, j: number) => (
115
+ <PeerEntry key={j} peer={peer} />
116
+ ))}
117
+ </div>
118
+ </div>
119
+ )}
120
+
121
+ {ports.length > 0 && (
122
+ <div>
123
+ <div className="text-xs text-theme-text-tertiary mb-1">Ports</div>
124
+ <div className="flex flex-wrap gap-1">
125
+ {ports.map((port: any, j: number) => (
126
+ <span
127
+ key={j}
128
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
129
+ >
130
+ {port.protocol || 'TCP'}/{port.port}
131
+ </span>
132
+ ))}
133
+ </div>
134
+ </div>
135
+ )}
136
+
137
+ {peers.length === 0 && ports.length === 0 && (
138
+ <div className="text-xs text-theme-text-tertiary">Allow all</div>
139
+ )}
140
+ </div>
141
+ )
142
+ }
143
+
144
+ function PeerEntry({ peer }: { peer: any }) {
145
+ if (peer.podSelector) {
146
+ const labels = peer.podSelector.matchLabels || {}
147
+ const hasLabels = Object.keys(labels).length > 0
148
+ return (
149
+ <div className="text-sm">
150
+ <span className="text-theme-text-secondary text-xs">podSelector: </span>
151
+ {hasLabels ? (
152
+ <span className="inline-flex flex-wrap gap-1 align-middle">
153
+ {Object.entries(labels).map(([k, v]) => (
154
+ <span
155
+ key={k}
156
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
157
+ >
158
+ {k}={String(v)}
159
+ </span>
160
+ ))}
161
+ </span>
162
+ ) : (
163
+ <span className="text-xs text-theme-text-tertiary">all pods</span>
164
+ )}
165
+ </div>
166
+ )
167
+ }
168
+
169
+ if (peer.namespaceSelector) {
170
+ const labels = peer.namespaceSelector.matchLabels || {}
171
+ const hasLabels = Object.keys(labels).length > 0
172
+ return (
173
+ <div className="text-sm">
174
+ <span className="text-theme-text-secondary text-xs">namespaceSelector: </span>
175
+ {hasLabels ? (
176
+ <span className="inline-flex flex-wrap gap-1 align-middle">
177
+ {Object.entries(labels).map(([k, v]) => (
178
+ <span
179
+ key={k}
180
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
181
+ >
182
+ {k}={String(v)}
183
+ </span>
184
+ ))}
185
+ </span>
186
+ ) : (
187
+ <span className="text-xs text-theme-text-tertiary">all namespaces</span>
188
+ )}
189
+ </div>
190
+ )
191
+ }
192
+
193
+ if (peer.ipBlock) {
194
+ return (
195
+ <div className="text-sm">
196
+ <span className="text-theme-text-secondary text-xs">ipBlock: </span>
197
+ <span className="text-xs text-theme-text-primary">{peer.ipBlock.cidr}</span>
198
+ {peer.ipBlock.except && peer.ipBlock.except.length > 0 && (
199
+ <span className="text-xs text-theme-text-tertiary">
200
+ {' '}except {peer.ipBlock.except.join(', ')}
201
+ </span>
202
+ )}
203
+ </div>
204
+ )
205
+ }
206
+
207
+ return null
208
+ }