@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,111 @@
1
+ import { Shield, AlertTriangle, Globe, Key } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
4
+ import { getSolverType } from './ClusterIssuerRenderer'
5
+
6
+ function getChallengeStateBadge(state: string): { color: string; text: string } {
7
+ switch (state?.toLowerCase()) {
8
+ case 'valid':
9
+ return { text: 'Valid', color: 'bg-green-500/20 text-green-400' }
10
+ case 'ready':
11
+ return { text: 'Ready', color: 'bg-blue-500/20 text-blue-400' }
12
+ case 'pending':
13
+ return { text: 'Pending', color: 'bg-yellow-500/20 text-yellow-400' }
14
+ case 'processing':
15
+ return { text: 'Processing', color: 'bg-yellow-500/20 text-yellow-400' }
16
+ case 'invalid':
17
+ return { text: 'Invalid', color: 'bg-red-500/20 text-red-400' }
18
+ case 'expired':
19
+ return { text: 'Expired', color: 'bg-red-500/20 text-red-400' }
20
+ case 'errored':
21
+ return { text: 'Errored', color: 'bg-red-500/20 text-red-400' }
22
+ default:
23
+ return { text: state || 'Unknown', color: 'bg-gray-500/20 text-gray-400' }
24
+ }
25
+ }
26
+
27
+ export function ChallengeRenderer({ data }: { data: any }) {
28
+ const spec = data.spec || {}
29
+ const status = data.status || {}
30
+ const conditions = status.conditions || []
31
+ const state = status.state || ''
32
+ const presented = status.presented
33
+
34
+ const isError = state === 'invalid' || state === 'expired' || state === 'errored'
35
+
36
+ // Determine challenge type from spec
37
+ const challengeType = spec.type || (spec.solver?.http01 ? 'HTTP-01' : spec.solver?.dns01 ? 'DNS-01' : 'Unknown')
38
+
39
+ // Extract solver info
40
+ const solver = spec.solver || {}
41
+ const solverInfo = getSolverType(solver)
42
+
43
+ return (
44
+ <>
45
+ {/* Problem detection alert */}
46
+ {isError && (
47
+ <div className="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg">
48
+ <div className="flex items-start gap-2">
49
+ <AlertTriangle className="w-4 h-4 text-red-400 mt-0.5 shrink-0" />
50
+ <div className="flex-1 min-w-0">
51
+ <div className="text-sm font-medium text-red-400">Challenge {state.charAt(0).toUpperCase() + state.slice(1)}</div>
52
+ {status.reason && (
53
+ <div className="text-xs text-red-300/80 mt-1">{status.reason}</div>
54
+ )}
55
+ </div>
56
+ </div>
57
+ </div>
58
+ )}
59
+
60
+ {/* Status */}
61
+ <Section title="Status" icon={Shield}>
62
+ <PropertyList>
63
+ <Property
64
+ label="State"
65
+ value={
66
+ <span className={clsx('px-2 py-0.5 rounded text-xs font-medium', getChallengeStateBadge(state).color)}>
67
+ {getChallengeStateBadge(state).text}
68
+ </span>
69
+ }
70
+ />
71
+ <Property
72
+ label="Type"
73
+ value={
74
+ <span className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary font-medium">
75
+ {challengeType}
76
+ </span>
77
+ }
78
+ />
79
+ <Property
80
+ label="Presented"
81
+ value={presented === true ? 'Yes' : presented === false ? 'No' : '-'}
82
+ />
83
+ {spec.dnsName && <Property label="Domain" value={spec.dnsName} />}
84
+ </PropertyList>
85
+ </Section>
86
+
87
+ {/* ACME */}
88
+ {(spec.url || spec.token) && (
89
+ <Section title="ACME" icon={Globe}>
90
+ <PropertyList>
91
+ {spec.url && <Property label="Challenge URL" value={spec.url} />}
92
+ {spec.token && <Property label="Token" value={spec.token.length > 32 ? spec.token.slice(0, 32) + '...' : spec.token} />}
93
+ </PropertyList>
94
+ </Section>
95
+ )}
96
+
97
+ {/* Solver */}
98
+ {solverInfo.type !== 'Unknown' && (
99
+ <Section title="Solver" icon={Key}>
100
+ <PropertyList>
101
+ <Property label="Type" value={solverInfo.type} />
102
+ {solverInfo.detail && <Property label="Detail" value={solverInfo.detail} />}
103
+ </PropertyList>
104
+ </Section>
105
+ )}
106
+
107
+ {/* Conditions */}
108
+ <ConditionsSection conditions={conditions} />
109
+ </>
110
+ )
111
+ }
@@ -0,0 +1,164 @@
1
+ import { useState } from 'react'
2
+ import { ShieldCheck, ChevronDown, ChevronRight, CheckCircle2, XCircle } from 'lucide-react'
3
+ import { clsx } from 'clsx'
4
+ import { Section, PropertyList, Property, AlertBanner } from '../../ui/drawer-components'
5
+ import { formatAge } from '../resource-utils'
6
+ import { SEVERITY_BADGE_COLORS, SEVERITY_ORDER } from './trivy-shared'
7
+
8
+ interface ClusterComplianceReportRendererProps {
9
+ data: any
10
+ }
11
+
12
+ export function ClusterComplianceReportRenderer({ data }: ClusterComplianceReportRendererProps) {
13
+ const [expandedSection, setExpandedSection] = useState(true)
14
+ const [expandedControls, setExpandedControls] = useState<Set<string>>(new Set())
15
+
16
+ const compliance = data.spec?.compliance || {}
17
+ const status = data.status || {}
18
+ const summaryReport = status.summaryReport || {}
19
+ const controlChecks = Array.isArray(summaryReport.controlCheck) ? summaryReport.controlCheck : []
20
+ const controls = Array.isArray(compliance.controls) ? compliance.controls : []
21
+
22
+ const passCount = status.summary?.passCount || 0
23
+ const failCount = status.summary?.failCount || 0
24
+ const total = passCount + failCount
25
+
26
+ // Build a map of control definitions for descriptions
27
+ const controlMap = new Map<string, any>()
28
+ for (const ctrl of controls) {
29
+ if (ctrl.id) controlMap.set(ctrl.id, ctrl)
30
+ }
31
+
32
+ // Sort: failed first by severity, then passed
33
+ const sortedChecks = [...controlChecks].sort((a: any, b: any) => {
34
+ const aFail = (a.totalFail || 0) > 0
35
+ const bFail = (b.totalFail || 0) > 0
36
+ if (aFail !== bFail) return aFail ? -1 : 1
37
+ return (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b.severity] ?? 99)
38
+ })
39
+
40
+ const toggleControl = (id: string) => {
41
+ setExpandedControls(prev => {
42
+ const next = new Set(prev)
43
+ if (next.has(id)) next.delete(id)
44
+ else next.add(id)
45
+ return next
46
+ })
47
+ }
48
+
49
+ return (
50
+ <>
51
+ {/* Alert banner for failures */}
52
+ {failCount > 0 && (
53
+ <AlertBanner
54
+ variant="error"
55
+ icon={XCircle}
56
+ title={`${failCount} control${failCount !== 1 ? 's' : ''} failed`}
57
+ message="Review failed controls to improve cluster compliance."
58
+ />
59
+ )}
60
+
61
+ {/* Framework Overview */}
62
+ <Section title="Compliance Framework" icon={ShieldCheck}>
63
+ <PropertyList>
64
+ <Property label="Framework" value={compliance.title || compliance.id || '-'} />
65
+ {compliance.description && <Property label="Description" value={compliance.description} />}
66
+ {compliance.version && <Property label="Version" value={compliance.version} />}
67
+ {compliance.platform && <Property label="Platform" value={compliance.platform} />}
68
+ <Property label="Last Updated" value={status.updateTimestamp ? formatAge(status.updateTimestamp) + ' ago' : '-'} />
69
+ </PropertyList>
70
+ </Section>
71
+
72
+ {/* Summary */}
73
+ <Section title="Summary">
74
+ <div className="flex items-center gap-4 mb-3">
75
+ <div className="flex items-center gap-1.5">
76
+ <CheckCircle2 className="w-4 h-4 text-green-400" />
77
+ <span className="text-sm font-medium text-green-400">{passCount}</span>
78
+ <span className="text-xs text-theme-text-tertiary">passed</span>
79
+ </div>
80
+ <div className="flex items-center gap-1.5">
81
+ <XCircle className="w-4 h-4 text-red-400" />
82
+ <span className="text-sm font-medium text-red-400">{failCount}</span>
83
+ <span className="text-xs text-theme-text-tertiary">failed</span>
84
+ </div>
85
+ </div>
86
+ {total > 0 && (
87
+ <div className="h-3 rounded overflow-hidden flex">
88
+ {passCount > 0 && <div className="h-full bg-green-500" style={{ width: `${(passCount / total) * 100}%` }} />}
89
+ {failCount > 0 && <div className="h-full bg-red-500" style={{ width: `${(failCount / total) * 100}%` }} />}
90
+ </div>
91
+ )}
92
+ </Section>
93
+
94
+ {/* Controls */}
95
+ {sortedChecks.length > 0 && (
96
+ <Section title="Controls">
97
+ <button
98
+ onClick={() => setExpandedSection(!expandedSection)}
99
+ className="flex items-center gap-1 text-xs text-theme-text-secondary hover:text-theme-text-primary mb-2"
100
+ >
101
+ {expandedSection ? <ChevronDown className="w-3.5 h-3.5" /> : <ChevronRight className="w-3.5 h-3.5" />}
102
+ {sortedChecks.length} controls
103
+ </button>
104
+ {expandedSection && (
105
+ <div className="space-y-0.5">
106
+ {sortedChecks.map((check: any) => {
107
+ const hasFail = (check.totalFail || 0) > 0
108
+ const controlDef = controlMap.get(check.id)
109
+ const isExpanded = expandedControls.has(check.id)
110
+ return (
111
+ <div key={check.id}>
112
+ <button
113
+ onClick={() => toggleControl(check.id)}
114
+ className="w-full flex items-center gap-2 py-1.5 px-1 rounded hover:bg-theme-hover/50 text-left"
115
+ >
116
+ {hasFail ? (
117
+ <XCircle className="w-3.5 h-3.5 text-red-400 shrink-0" />
118
+ ) : (
119
+ <CheckCircle2 className="w-3.5 h-3.5 text-green-400 shrink-0" />
120
+ )}
121
+ <span className={clsx('px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0', SEVERITY_BADGE_COLORS[check.severity] || 'bg-gray-500/20 text-gray-400')}>
122
+ {check.severity || '-'}
123
+ </span>
124
+ <span className="text-xs text-theme-text-secondary truncate flex-1">{check.name || check.id}</span>
125
+ <span className="text-[10px] text-theme-text-tertiary shrink-0">
126
+ {check.totalFail || 0}F / {check.totalPass || 0}P
127
+ </span>
128
+ {isExpanded ? (
129
+ <ChevronDown className="w-3 h-3 text-theme-text-tertiary shrink-0" />
130
+ ) : (
131
+ <ChevronRight className="w-3 h-3 text-theme-text-tertiary shrink-0" />
132
+ )}
133
+ </button>
134
+ {isExpanded && controlDef && (
135
+ <div className="ml-6 mr-1 mb-2 p-2.5 bg-theme-elevated/50 rounded border border-theme-border/50 space-y-2">
136
+ <div className="text-[10px] font-mono text-theme-text-tertiary">{check.id}</div>
137
+ {controlDef.description && (
138
+ <div>
139
+ <div className="text-[10px] font-medium text-theme-text-tertiary uppercase tracking-wider mb-0.5">Description</div>
140
+ <div className="text-xs text-theme-text-secondary">{controlDef.description}</div>
141
+ </div>
142
+ )}
143
+ {controlDef.checks?.length > 0 && (
144
+ <div>
145
+ <div className="text-[10px] font-medium text-theme-text-tertiary uppercase tracking-wider mb-0.5">Check IDs</div>
146
+ <div className="flex flex-wrap gap-1">
147
+ {controlDef.checks.map((c: any, i: number) => (
148
+ <span key={i} className="text-[10px] font-mono text-theme-text-tertiary bg-theme-surface px-1.5 py-0.5 rounded">{c.id}</span>
149
+ ))}
150
+ </div>
151
+ </div>
152
+ )}
153
+ </div>
154
+ )}
155
+ </div>
156
+ )
157
+ })}
158
+ </div>
159
+ )}
160
+ </Section>
161
+ )}
162
+ </>
163
+ )
164
+ }
@@ -0,0 +1,140 @@
1
+ import { Globe, AlertTriangle } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import {
4
+ getClusterExternalSecretStatus,
5
+ getClusterExternalSecretProvisionedNamespaces,
6
+ getClusterExternalSecretFailedNamespaces,
7
+ getClusterExternalSecretNamespaces,
8
+ getClusterExternalSecretNamespaceSelector,
9
+ } from '../resource-utils-eso'
10
+
11
+ interface ClusterExternalSecretRendererProps {
12
+ data: any
13
+ }
14
+
15
+ export function ClusterExternalSecretRenderer({ data }: ClusterExternalSecretRendererProps) {
16
+ const status = data.status || {}
17
+ const conditions = status.conditions || []
18
+
19
+ const cesStatus = getClusterExternalSecretStatus(data)
20
+ const provisionedNamespaces = getClusterExternalSecretProvisionedNamespaces(data)
21
+ const failedNamespaces = getClusterExternalSecretFailedNamespaces(data)
22
+ const explicitNamespaces = getClusterExternalSecretNamespaces(data)
23
+ const namespaceSelector = getClusterExternalSecretNamespaceSelector(data)
24
+
25
+ // ExternalSecret spec embedded in the ClusterExternalSecret
26
+ const esSpec = data.spec?.externalSecretSpec
27
+ const refreshInterval = esSpec?.refreshInterval || '-'
28
+ const storeRef = esSpec?.secretStoreRef
29
+ const dataCount = (esSpec?.data || []).length
30
+ const dataFromCount = (esSpec?.dataFrom || []).length
31
+
32
+ return (
33
+ <>
34
+ {/* Alert for failed namespaces */}
35
+ {failedNamespaces.length > 0 && (
36
+ <AlertBanner
37
+ variant="error"
38
+ title={`Failed in ${failedNamespaces.length} namespace${failedNamespaces.length > 1 ? 's' : ''}`}
39
+ message="ExternalSecret provisioning failed in one or more namespaces."
40
+ items={failedNamespaces.map(f => `${f.namespace}${f.reason ? `: ${f.reason}` : ''}`)}
41
+ />
42
+ )}
43
+
44
+ {/* Overview */}
45
+ <Section title="Overview" icon={Globe} defaultExpanded>
46
+ <PropertyList>
47
+ <Property label="Status" value={cesStatus.text} />
48
+ <Property label="Provisioned" value={`${provisionedNamespaces.length} namespace${provisionedNamespaces.length !== 1 ? 's' : ''}`} />
49
+ {failedNamespaces.length > 0 && (
50
+ <Property label="Failed" value={`${failedNamespaces.length} namespace${failedNamespaces.length !== 1 ? 's' : ''}`} />
51
+ )}
52
+ </PropertyList>
53
+ </Section>
54
+
55
+ {/* Namespace Targeting */}
56
+ <Section title="Namespace Selection" defaultExpanded>
57
+ {explicitNamespaces.length > 0 ? (
58
+ <div>
59
+ <div className="text-xs text-theme-text-secondary mb-1.5">Explicit Namespaces</div>
60
+ <div className="flex flex-wrap gap-1">
61
+ {explicitNamespaces.map((ns: string) => (
62
+ <span key={ns} className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-primary">
63
+ {ns}
64
+ </span>
65
+ ))}
66
+ </div>
67
+ </div>
68
+ ) : namespaceSelector ? (
69
+ <div>
70
+ <div className="text-xs text-theme-text-secondary mb-1.5">Label Selector</div>
71
+ <div className="flex flex-wrap gap-1">
72
+ {Object.entries(namespaceSelector).map(([k, v]) => (
73
+ <span key={k} className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary">
74
+ {k}={v}
75
+ </span>
76
+ ))}
77
+ </div>
78
+ </div>
79
+ ) : (
80
+ <div className="text-xs text-theme-text-tertiary">All namespaces (no selector)</div>
81
+ )}
82
+ </Section>
83
+
84
+ {/* Provisioned Namespaces */}
85
+ {provisionedNamespaces.length > 0 && (
86
+ <Section title={`Provisioned Namespaces (${provisionedNamespaces.length})`} defaultExpanded={provisionedNamespaces.length <= 10}>
87
+ <div className="flex flex-wrap gap-1">
88
+ {provisionedNamespaces.map((ns: string) => (
89
+ <span key={ns} className="px-2 py-0.5 bg-green-500/10 text-green-400 rounded text-xs">
90
+ {ns}
91
+ </span>
92
+ ))}
93
+ </div>
94
+ </Section>
95
+ )}
96
+
97
+ {/* Failed Namespaces (detailed) */}
98
+ {failedNamespaces.length > 0 && (
99
+ <Section title={`Failed Namespaces (${failedNamespaces.length})`} defaultExpanded>
100
+ <div className="space-y-1">
101
+ {failedNamespaces.map((f, i) => (
102
+ <div key={i} className="bg-red-500/10 rounded p-2 flex items-start gap-2">
103
+ <AlertTriangle className="w-3.5 h-3.5 text-red-400 mt-0.5 shrink-0" />
104
+ <div className="text-xs">
105
+ <span className="text-red-400 font-medium">{f.namespace}</span>
106
+ {f.reason && (
107
+ <span className="text-red-300/80 ml-1 break-all">{f.reason}</span>
108
+ )}
109
+ </div>
110
+ </div>
111
+ ))}
112
+ </div>
113
+ </Section>
114
+ )}
115
+
116
+ {/* External Secret Spec Summary */}
117
+ <Section title="ExternalSecret Spec" defaultExpanded>
118
+ <PropertyList>
119
+ <Property label="Refresh Interval" value={refreshInterval} />
120
+ {storeRef && (
121
+ <>
122
+ <Property label="Store Name" value={storeRef.name || '-'} />
123
+ <Property label="Store Kind" value={storeRef.kind || 'SecretStore'} />
124
+ </>
125
+ )}
126
+ <Property label="Data Mappings" value={dataCount > 0 ? String(dataCount) : '-'} />
127
+ <Property label="Data Sources" value={dataFromCount > 0 ? String(dataFromCount) : '-'} />
128
+ {esSpec?.target?.creationPolicy && (
129
+ <Property label="Creation Policy" value={esSpec.target.creationPolicy} />
130
+ )}
131
+ {esSpec?.target?.deletionPolicy && (
132
+ <Property label="Deletion Policy" value={esSpec.target.deletionPolicy} />
133
+ )}
134
+ </PropertyList>
135
+ </Section>
136
+
137
+ <ConditionsSection conditions={conditions} />
138
+ </>
139
+ )
140
+ }
@@ -0,0 +1,153 @@
1
+ import { Shield, AlertTriangle, Globe, Lock, Key } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
4
+
5
+ function detectIssuerType(spec: any): string {
6
+ if (spec.acme) return 'ACME'
7
+ if (spec.ca) return 'CA'
8
+ if (spec.selfSigned !== undefined) return 'SelfSigned'
9
+ if (spec.vault) return 'Vault'
10
+ return 'Unknown'
11
+ }
12
+
13
+ export function getSolverType(solver: any): { type: string; detail: string } {
14
+ if (solver.http01) {
15
+ const ingressClass = solver.http01.ingress?.class || solver.http01.ingress?.ingressClassName
16
+ return { type: 'HTTP-01', detail: ingressClass ? `Ingress class: ${ingressClass}` : 'Ingress solver' }
17
+ }
18
+ if (solver.dns01) {
19
+ const dns = solver.dns01
20
+ if (dns.cloudDNS) return { type: 'DNS-01', detail: `Cloud DNS (project: ${dns.cloudDNS.project})` }
21
+ if (dns.route53) return { type: 'DNS-01', detail: `Route 53 (region: ${dns.route53.region || 'default'})` }
22
+ if (dns.cloudflare) return { type: 'DNS-01', detail: 'Cloudflare' }
23
+ if (dns.digitalocean) return { type: 'DNS-01', detail: 'DigitalOcean' }
24
+ if (dns.azureDNS) return { type: 'DNS-01', detail: 'Azure DNS' }
25
+ if (dns.akamai) return { type: 'DNS-01', detail: 'Akamai' }
26
+ if (dns.rfc2136) return { type: 'DNS-01', detail: 'RFC2136' }
27
+ if (dns.webhook) return { type: 'DNS-01', detail: `Webhook (${dns.webhook.groupName || 'custom'})` }
28
+ return { type: 'DNS-01', detail: `Provider: ${Object.keys(dns).join(', ')}` }
29
+ }
30
+ return { type: 'Unknown', detail: '' }
31
+ }
32
+
33
+ function IssuerRendererBase({ data, kind }: { data: any; kind: string }) {
34
+ const spec = data.spec || {}
35
+ const status = data.status || {}
36
+ const conditions = status.conditions || []
37
+
38
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
39
+ const isReady = readyCond?.status === 'True'
40
+ const isNotReady = readyCond?.status === 'False'
41
+
42
+ const issuerType = detectIssuerType(spec)
43
+
44
+ const acme = spec.acme
45
+ const acmeStatus = status.acme
46
+ const solvers = acme?.solvers || []
47
+
48
+ return (
49
+ <>
50
+ {/* Problem detection alert */}
51
+ {isNotReady && (
52
+ <div className="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg">
53
+ <div className="flex items-start gap-2">
54
+ <AlertTriangle className="w-4 h-4 text-red-400 mt-0.5 shrink-0" />
55
+ <div className="flex-1 min-w-0">
56
+ <div className="text-sm font-medium text-red-400">{kind} Not Ready</div>
57
+ <div className="text-xs text-red-300/80 mt-1">
58
+ {readyCond.reason && <span className="font-medium">{readyCond.reason}: </span>}
59
+ {readyCond.message || 'The issuer is not in a ready state.'}
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ )}
65
+
66
+ {/* Status */}
67
+ <Section title="Status" icon={Shield}>
68
+ <PropertyList>
69
+ <Property
70
+ label="Ready"
71
+ value={
72
+ <span className={clsx(
73
+ 'px-2 py-0.5 rounded text-xs font-medium',
74
+ isReady
75
+ ? 'bg-green-500/20 text-green-400'
76
+ : 'bg-red-500/20 text-red-400'
77
+ )}>
78
+ {isReady ? 'Ready' : 'Not Ready'}
79
+ </span>
80
+ }
81
+ />
82
+ <Property label="Issuer Type" value={issuerType} />
83
+ </PropertyList>
84
+ </Section>
85
+
86
+ {/* ACME section */}
87
+ {acme && (
88
+ <Section title="ACME" icon={Globe}>
89
+ <PropertyList>
90
+ <Property label="Server" value={acme.server} />
91
+ <Property label="Email" value={acme.email} />
92
+ <Property label="Private Key Secret" value={acme.privateKeySecretRef?.name} />
93
+ <Property label="Registered Email" value={acmeStatus?.lastRegisteredEmail} />
94
+ <Property label="Account URI" value={acmeStatus?.uri} />
95
+ </PropertyList>
96
+ </Section>
97
+ )}
98
+
99
+ {/* Solvers section */}
100
+ {solvers.length > 0 && (
101
+ <Section title={`Solvers (${solvers.length})`} icon={Key}>
102
+ <div className="space-y-2">
103
+ {solvers.map((solver: any, i: number) => {
104
+ const { type, detail } = getSolverType(solver)
105
+ return (
106
+ <div key={i} className="bg-theme-elevated/30 rounded p-3">
107
+ <div className="flex items-center gap-2">
108
+ <span className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary font-medium">
109
+ {type}
110
+ </span>
111
+ {detail && (
112
+ <span className="text-xs text-theme-text-tertiary">{detail}</span>
113
+ )}
114
+ </div>
115
+ {solver.selector && (
116
+ <div className="mt-2 text-xs text-theme-text-tertiary">
117
+ {solver.selector.dnsNames && (
118
+ <div>DNS Names: {solver.selector.dnsNames.join(', ')}</div>
119
+ )}
120
+ {solver.selector.dnsZones && (
121
+ <div>DNS Zones: {solver.selector.dnsZones.join(', ')}</div>
122
+ )}
123
+ </div>
124
+ )}
125
+ </div>
126
+ )
127
+ })}
128
+ </div>
129
+ </Section>
130
+ )}
131
+
132
+ {/* CA section */}
133
+ {spec.ca && (
134
+ <Section title="CA" icon={Lock}>
135
+ <PropertyList>
136
+ <Property label="Secret Name" value={spec.ca.secretName} />
137
+ </PropertyList>
138
+ </Section>
139
+ )}
140
+
141
+ {/* Conditions */}
142
+ <ConditionsSection conditions={conditions} />
143
+ </>
144
+ )
145
+ }
146
+
147
+ export function ClusterIssuerRenderer({ data }: { data: any }) {
148
+ return <IssuerRendererBase data={data} kind="ClusterIssuer" />
149
+ }
150
+
151
+ export function IssuerRenderer({ data }: { data: any }) {
152
+ return <IssuerRendererBase data={data} kind="Issuer" />
153
+ }