@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,321 @@
1
+ import { useState, useCallback, useRef, useEffect } from 'react'
2
+ import { AlertTriangle, Copy, Check, Shield, Pencil, Save, XCircle, RefreshCw } from 'lucide-react'
3
+ import { clsx } from 'clsx'
4
+ import { stringify as yamlStringify } from 'yaml'
5
+ import { Section, PropertyList, Property, AlertBanner } from '../../ui/drawer-components'
6
+ import { ConfirmDialog } from '../../ui/ConfirmDialog'
7
+ import type { SecretCertificateInfo, CertificateInfo } from '../../../types'
8
+
9
+ interface SecretRendererProps {
10
+ data: any
11
+ certificateInfo?: SecretCertificateInfo
12
+ resourceData?: any
13
+ onSaveSecretValue?: (yaml: string) => Promise<void>
14
+ isSaving?: boolean
15
+ }
16
+
17
+ function formatDate(dateStr: string): string {
18
+ if (!dateStr) return '-'
19
+ const d = new Date(dateStr)
20
+ return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
21
+ }
22
+
23
+ export function SecretRenderer({ data, certificateInfo, resourceData, onSaveSecretValue, isSaving }: SecretRendererProps) {
24
+ const [revealed, setRevealed] = useState<Set<string>>(new Set())
25
+ const [copied, setCopied] = useState<string | null>(null)
26
+ const [editingKey, setEditingKey] = useState<string | null>(null)
27
+ const [editValue, setEditValue] = useState('')
28
+ const [showSaveConfirm, setShowSaveConfirm] = useState(false)
29
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
30
+ const dataKeys = Object.keys(data.data || {})
31
+ const isImmutable = data.immutable === true
32
+
33
+ function toggleReveal(key: string) {
34
+ setRevealed(prev => {
35
+ const next = new Set(prev)
36
+ next.has(key) ? next.delete(key) : next.add(key)
37
+ return next
38
+ })
39
+ }
40
+
41
+ function decodeBase64(value: string): string {
42
+ try {
43
+ return decodeURIComponent(escape(atob(value)))
44
+ } catch {
45
+ return '[binary data]'
46
+ }
47
+ }
48
+
49
+ async function copyValue(key: string, decodedValue: string): Promise<void> {
50
+ try {
51
+ await navigator.clipboard.writeText(decodedValue)
52
+ setCopied(key)
53
+ setTimeout(() => setCopied(null), 2000)
54
+ } catch (err) {
55
+ console.error('Failed to copy:', err)
56
+ }
57
+ }
58
+
59
+ function startEdit(key: string, decoded: string) {
60
+ setEditingKey(key)
61
+ setEditValue(decoded)
62
+ }
63
+
64
+ function cancelEdit() {
65
+ setEditingKey(null)
66
+ setEditValue('')
67
+ setShowSaveConfirm(false)
68
+ }
69
+
70
+ const handleSave = useCallback(async (key: string, newValue: string) => {
71
+ if (!onSaveSecretValue || !resourceData) return
72
+ const cleaned = structuredClone(resourceData)
73
+ delete cleaned.status
74
+ if (cleaned.metadata) {
75
+ delete cleaned.metadata.managedFields
76
+ delete cleaned.metadata.resourceVersion
77
+ delete cleaned.metadata.uid
78
+ delete cleaned.metadata.creationTimestamp
79
+ delete cleaned.metadata.generation
80
+ }
81
+ // Encode with UTF-8 support
82
+ cleaned.data[key] = btoa(unescape(encodeURIComponent(newValue)))
83
+ const yaml = yamlStringify(cleaned, { lineWidth: 0, indent: 2 })
84
+ try {
85
+ await onSaveSecretValue(yaml)
86
+ setEditingKey(null)
87
+ setEditValue('')
88
+ setShowSaveConfirm(false)
89
+ } catch {
90
+ // Error is handled by the mutation (toast)
91
+ setShowSaveConfirm(false)
92
+ }
93
+ }, [onSaveSecretValue, resourceData])
94
+
95
+ // Auto-resize textarea
96
+ useEffect(() => {
97
+ if (textareaRef.current) {
98
+ textareaRef.current.style.height = 'auto'
99
+ textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'
100
+ }
101
+ }, [editValue])
102
+
103
+ const certs = certificateInfo?.certificates
104
+ const leafCert = certs?.[0]
105
+
106
+ return (
107
+ <>
108
+ <Section title="Secret">
109
+ <PropertyList>
110
+ <Property label="Type" value={data.type || 'Opaque'} />
111
+ <Property label="Keys" value={String(dataKeys.length)} />
112
+ {data.immutable && <Property label="Immutable" value="Yes" />}
113
+ </PropertyList>
114
+ </Section>
115
+
116
+ {/* Certificate expiry alerts */}
117
+ {leafCert && leafCert.expired && (
118
+ <AlertBanner
119
+ variant="error"
120
+ title="Certificate has expired"
121
+ message={`Expired ${formatDate(leafCert.notAfter)}.${leafCert.daysLeft !== 0 ? ` ${Math.abs(leafCert.daysLeft)}d ago.` : ''}`}
122
+ />
123
+ )}
124
+
125
+ {leafCert && !leafCert.expired && leafCert.daysLeft <= 7 && (
126
+ <AlertBanner
127
+ variant="error"
128
+ title={`Certificate expires in ${leafCert.daysLeft} day${leafCert.daysLeft !== 1 ? 's' : ''}`}
129
+ message="Check that cert-manager or your CA is renewing this certificate."
130
+ />
131
+ )}
132
+
133
+ {leafCert && !leafCert.expired && leafCert.daysLeft > 7 && leafCert.daysLeft <= 30 && (
134
+ <AlertBanner
135
+ variant="warning"
136
+ title={`Certificate expires in ${leafCert.daysLeft} day${leafCert.daysLeft !== 1 ? 's' : ''}`}
137
+ message="Renewal should happen automatically before expiry."
138
+ />
139
+ )}
140
+
141
+ {/* Certificate info section */}
142
+ {certs && certs.length > 0 && (
143
+ <>
144
+ {certs.map((cert, i) => (
145
+ <CertificateInfoSection
146
+ key={cert.serialNumber}
147
+ cert={cert}
148
+ index={i}
149
+ total={certs.length}
150
+ />
151
+ ))}
152
+ </>
153
+ )}
154
+
155
+ <Section title="Data" defaultExpanded>
156
+ <div className="space-y-2">
157
+ {dataKeys.map((key) => {
158
+ const decoded = decodeBase64(data.data[key])
159
+ const isBinary = decoded === '[binary data]'
160
+ const isEditing = editingKey === key
161
+ const canEdit = onSaveSecretValue && !isImmutable && !isBinary
162
+
163
+ return (
164
+ <div key={key} className="bg-theme-elevated/30 rounded p-2">
165
+ <div className="flex items-center justify-between gap-2">
166
+ <span className="text-sm text-theme-text-primary truncate">{key}</span>
167
+ <div className="flex items-center gap-1 shrink-0">
168
+ {revealed.has(key) && !isBinary && !isEditing && canEdit && (
169
+ <button
170
+ onClick={() => startEdit(key, decoded)}
171
+ className="p-1 text-theme-text-tertiary hover:text-blue-400 transition-colors"
172
+ title="Edit value"
173
+ >
174
+ <Pencil className="w-3.5 h-3.5" />
175
+ </button>
176
+ )}
177
+ {revealed.has(key) && !isBinary && !isEditing && (
178
+ <button
179
+ onClick={() => copyValue(key, decoded)}
180
+ className="p-1 text-theme-text-tertiary hover:text-theme-text-primary transition-colors"
181
+ title="Copy value"
182
+ >
183
+ {copied === key ? (
184
+ <Check className="w-3.5 h-3.5 text-green-400" />
185
+ ) : (
186
+ <Copy className="w-3.5 h-3.5" />
187
+ )}
188
+ </button>
189
+ )}
190
+ {!isEditing && (
191
+ <button
192
+ onClick={() => toggleReveal(key)}
193
+ className="text-xs text-theme-text-secondary hover:text-theme-text-primary px-1.5 py-0.5 rounded hover:bg-theme-elevated transition-colors"
194
+ >
195
+ {revealed.has(key) ? 'Hide' : 'Reveal'}
196
+ </button>
197
+ )}
198
+ </div>
199
+ </div>
200
+ {isEditing ? (
201
+ <div className="mt-2">
202
+ <textarea
203
+ ref={textareaRef}
204
+ value={editValue}
205
+ onChange={(e) => setEditValue(e.target.value)}
206
+ className="w-full bg-theme-base rounded p-2 text-xs text-theme-text-secondary font-mono border border-blue-500/50 focus:border-blue-500 focus:outline-none resize-none overflow-hidden whitespace-pre-wrap"
207
+ style={{ minHeight: '60px' }}
208
+ disabled={isSaving}
209
+ />
210
+ <div className="flex items-center gap-2 mt-2">
211
+ <button
212
+ onClick={() => setShowSaveConfirm(true)}
213
+ disabled={isSaving}
214
+ className="flex items-center gap-1 px-2.5 py-1 text-xs text-white bg-blue-600 hover:bg-blue-700 rounded transition-colors disabled:opacity-50"
215
+ >
216
+ {isSaving ? (
217
+ <RefreshCw className="w-3 h-3 animate-spin" />
218
+ ) : (
219
+ <Save className="w-3 h-3" />
220
+ )}
221
+ {isSaving ? 'Saving...' : 'Save'}
222
+ </button>
223
+ <button
224
+ onClick={cancelEdit}
225
+ disabled={isSaving}
226
+ className="flex items-center gap-1 px-2.5 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary rounded hover:bg-theme-elevated transition-colors disabled:opacity-50"
227
+ >
228
+ <XCircle className="w-3 h-3" />
229
+ Cancel
230
+ </button>
231
+ </div>
232
+ </div>
233
+ ) : revealed.has(key) ? (
234
+ <pre className="mt-2 bg-theme-base rounded p-2 text-xs text-theme-text-secondary overflow-x-auto max-h-40 whitespace-pre-wrap">
235
+ {decoded}
236
+ </pre>
237
+ ) : null}
238
+ </div>
239
+ )
240
+ })}
241
+ {dataKeys.length === 0 && (
242
+ <div className="text-sm text-theme-text-tertiary">No data</div>
243
+ )}
244
+ </div>
245
+ </Section>
246
+
247
+ <div className="flex items-center gap-2 p-3 bg-red-500/10 border border-red-500/30 rounded text-red-400 text-sm">
248
+ <AlertTriangle className="w-4 h-4" />
249
+ Secret values are sensitive. Be careful when revealing.
250
+ </div>
251
+
252
+ {editingKey && (
253
+ <ConfirmDialog
254
+ open={showSaveConfirm}
255
+ onClose={() => setShowSaveConfirm(false)}
256
+ onConfirm={() => handleSave(editingKey, editValue)}
257
+ title="Update Secret"
258
+ message={`Update key "${editingKey}" in secret "${data.metadata?.name || 'unknown'}"?`}
259
+ details="This will modify the secret value in the cluster immediately."
260
+ confirmLabel="Update"
261
+ variant="warning"
262
+ isLoading={isSaving}
263
+ />
264
+ )}
265
+ </>
266
+ )
267
+ }
268
+
269
+ function CertificateInfoSection({ cert, index, total }: { cert: CertificateInfo; index: number; total: number }) {
270
+ const expiryTextColor = cert.expired || cert.daysLeft <= 7
271
+ ? 'text-red-400'
272
+ : cert.daysLeft <= 30
273
+ ? 'text-yellow-400'
274
+ : 'text-green-400'
275
+
276
+ const title = total > 1
277
+ ? `Certificate ${index + 1} of ${total}`
278
+ : 'Certificate Info'
279
+
280
+ return (
281
+ <Section title={title} icon={Shield} defaultExpanded={index === 0}>
282
+ <PropertyList>
283
+ <Property label="Subject (CN)" value={cert.subject} />
284
+ {cert.sans && cert.sans.length > 0 && (
285
+ <Property label="SANs" value={
286
+ <div className="flex flex-wrap gap-1">
287
+ {cert.sans.map(san => (
288
+ <span key={san} className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary">
289
+ {san}
290
+ </span>
291
+ ))}
292
+ </div>
293
+ } />
294
+ )}
295
+ <Property label="Issuer" value={
296
+ <span>
297
+ {cert.issuer}
298
+ {cert.selfSigned && (
299
+ <span className="ml-2 text-[10px] px-1 py-0.5 bg-yellow-500/10 text-yellow-400 rounded">self-signed</span>
300
+ )}
301
+ </span>
302
+ } />
303
+ <Property label="Key Type" value={cert.keyType} />
304
+ <Property label="Serial" value={
305
+ <span className="font-mono text-xs">{cert.serialNumber}</span>
306
+ } />
307
+ <Property label="Not Before" value={formatDate(cert.notBefore)} />
308
+ <Property label="Expires" value={
309
+ <span>
310
+ {formatDate(cert.notAfter)}
311
+ <span className={clsx('ml-2 text-xs', expiryTextColor)}>
312
+ {cert.expired
313
+ ? `(expired ${Math.abs(cert.daysLeft)}d ago)`
314
+ : `(${cert.daysLeft}d remaining)`}
315
+ </span>
316
+ </span>
317
+ } />
318
+ </PropertyList>
319
+ </Section>
320
+ )
321
+ }
@@ -0,0 +1,111 @@
1
+ import { ShieldCheck } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
3
+ import {
4
+ getSecretStoreStatus,
5
+ getSecretStoreProviderType,
6
+ getSecretStoreProviderKey,
7
+ getSecretStoreProviderDetails,
8
+ getSecretStoreRetrySettings,
9
+ getSecretStoreController,
10
+ } from '../resource-utils-eso'
11
+
12
+ // Provider-specific color for visual distinction
13
+ function getProviderColor(providerKey: string): string {
14
+ switch (providerKey) {
15
+ case 'aws': return 'bg-orange-500/15 text-orange-400 border-orange-500/30'
16
+ case 'azurekv': return 'bg-blue-500/15 text-blue-400 border-blue-500/30'
17
+ case 'gcpsm': return 'bg-blue-500/15 text-blue-400 border-blue-500/30'
18
+ case 'vault': return 'bg-purple-500/15 text-purple-400 border-purple-500/30'
19
+ case 'kubernetes': return 'bg-cyan-500/15 text-cyan-400 border-cyan-500/30'
20
+ case 'doppler': return 'bg-green-500/15 text-green-400 border-green-500/30'
21
+ case 'onepassword': return 'bg-blue-500/15 text-blue-400 border-blue-500/30'
22
+ case 'akeyless': return 'bg-indigo-500/15 text-indigo-400 border-indigo-500/30'
23
+ default: return 'bg-theme-elevated text-theme-text-secondary border-theme-border'
24
+ }
25
+ }
26
+
27
+ interface SecretStoreRendererProps {
28
+ data: any
29
+ }
30
+
31
+ export function SecretStoreRenderer({ data }: SecretStoreRendererProps) {
32
+ const conditions = data.status?.conditions || []
33
+
34
+ const storeStatus = getSecretStoreStatus(data)
35
+ const providerType = getSecretStoreProviderType(data)
36
+ const providerKey = getSecretStoreProviderKey(data)
37
+ const providerDetails = getSecretStoreProviderDetails(data)
38
+ const retrySettings = getSecretStoreRetrySettings(data)
39
+ const controller = getSecretStoreController(data)
40
+
41
+ // Problem detection
42
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
43
+ const isNotReady = readyCond?.status === 'False'
44
+
45
+ return (
46
+ <>
47
+ {/* Alert if not ready */}
48
+ {isNotReady && (
49
+ <AlertBanner
50
+ variant="error"
51
+ title="Store Not Ready"
52
+ message={readyCond?.message || 'The SecretStore connection is not ready.'}
53
+ />
54
+ )}
55
+
56
+ {/* Provider section */}
57
+ <Section title="Provider" icon={ShieldCheck} defaultExpanded>
58
+ <div className="mb-3">
59
+ <span className={`inline-flex items-center px-2.5 py-1 rounded border text-sm font-medium ${getProviderColor(providerKey)}`}>
60
+ {providerType}
61
+ </span>
62
+ </div>
63
+
64
+ {providerDetails.length > 0 && (
65
+ <PropertyList>
66
+ {providerDetails.map((detail, i) => (
67
+ <Property key={i} label={detail.label} value={detail.value} />
68
+ ))}
69
+ </PropertyList>
70
+ )}
71
+
72
+ {controller && (
73
+ <div className="mt-2 pt-2 border-t border-theme-border">
74
+ <PropertyList>
75
+ <Property label="Controller" value={controller} />
76
+ </PropertyList>
77
+ </div>
78
+ )}
79
+ </Section>
80
+
81
+ {/* Connection Status */}
82
+ <Section title="Connection" defaultExpanded>
83
+ <PropertyList>
84
+ <Property label="Status" value={storeStatus.text} />
85
+ {readyCond?.reason && readyCond.reason !== storeStatus.text && (
86
+ <Property label="Reason" value={readyCond.reason} />
87
+ )}
88
+ {readyCond?.lastTransitionTime && (
89
+ <Property label="Last Transition" value={new Date(readyCond.lastTransitionTime).toLocaleString()} />
90
+ )}
91
+ </PropertyList>
92
+ </Section>
93
+
94
+ {/* Retry Settings */}
95
+ {retrySettings && (
96
+ <Section title="Retry Settings" defaultExpanded={false}>
97
+ <PropertyList>
98
+ {retrySettings.maxRetries !== undefined && (
99
+ <Property label="Max Retries" value={String(retrySettings.maxRetries)} />
100
+ )}
101
+ {retrySettings.retryInterval && (
102
+ <Property label="Retry Interval" value={retrySettings.retryInterval} />
103
+ )}
104
+ </PropertyList>
105
+ </Section>
106
+ )}
107
+
108
+ <ConditionsSection conditions={conditions} />
109
+ </>
110
+ )
111
+ }
@@ -0,0 +1,81 @@
1
+ import { UserCog, Key, Cloud } from 'lucide-react'
2
+ import { Section, PropertyList, Property } from '../../ui/drawer-components'
3
+
4
+ interface ServiceAccountRendererProps {
5
+ data: any
6
+ }
7
+
8
+ export function ServiceAccountRenderer({ data }: ServiceAccountRendererProps) {
9
+ const metadata = data.metadata || {}
10
+ const annotations = metadata.annotations || {}
11
+ const secrets = data.secrets || []
12
+ const imagePullSecrets = data.imagePullSecrets || []
13
+
14
+ // automountServiceAccountToken defaults to true if not set
15
+ const automountToken = data.automountServiceAccountToken !== false
16
+
17
+ // Workload identity annotations
18
+ const gcpServiceAccount = annotations['iam.gke.io/gcp-service-account']
19
+ const awsRoleArn = annotations['eks.amazonaws.com/role-arn']
20
+ const azureClientId = annotations['azure.workload.identity/client-id']
21
+ const hasWorkloadIdentity = gcpServiceAccount || awsRoleArn || azureClientId
22
+
23
+ return (
24
+ <>
25
+ {/* Configuration */}
26
+ <Section title="Configuration" icon={UserCog}>
27
+ <PropertyList>
28
+ <Property
29
+ label="Automount Token"
30
+ value={
31
+ <span
32
+ className={automountToken ? 'text-yellow-400' : 'text-green-400'}
33
+ title={automountToken ? 'Token is automatically mounted in pods' : undefined}
34
+ >
35
+ {automountToken ? 'Yes' : 'No'}
36
+ </span>
37
+ }
38
+ />
39
+ </PropertyList>
40
+ </Section>
41
+
42
+ {/* Workload Identity */}
43
+ {hasWorkloadIdentity && (
44
+ <Section title="Workload Identity" icon={Cloud}>
45
+ <PropertyList>
46
+ <Property label="GCP Service Account" value={gcpServiceAccount} />
47
+ <Property label="AWS Role ARN" value={awsRoleArn} />
48
+ <Property label="Azure Client ID" value={azureClientId} />
49
+ </PropertyList>
50
+ </Section>
51
+ )}
52
+
53
+ {/* Secrets */}
54
+ {secrets.length > 0 && (
55
+ <Section title={`Secrets (${secrets.length})`} icon={Key}>
56
+ <PropertyList>
57
+ {secrets.map((secret: any) => (
58
+ <Property key={secret.name} label="Secret" value={secret.name} />
59
+ ))}
60
+ </PropertyList>
61
+ </Section>
62
+ )}
63
+
64
+ {/* Image Pull Secrets */}
65
+ {imagePullSecrets.length > 0 && (
66
+ <Section title={`Image Pull Secrets (${imagePullSecrets.length})`}>
67
+ <div className="flex flex-wrap gap-1">
68
+ {imagePullSecrets.map((secret: any) => (
69
+ <span
70
+ key={secret.name}
71
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
72
+ >
73
+ {secret.name}
74
+ </span>
75
+ ))}
76
+ </div>
77
+ </Section>
78
+ )}
79
+ </>
80
+ )
81
+ }
@@ -0,0 +1,65 @@
1
+ import { Radio } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, KeyValueBadgeList } from '../../ui/drawer-components'
3
+ import {
4
+ getServiceMonitorEndpoints,
5
+ getServiceMonitorJobLabel,
6
+ getServiceMonitorNamespaceSelector,
7
+ } from '../resource-utils-prometheus'
8
+
9
+ interface ServiceMonitorRendererProps {
10
+ data: any
11
+ }
12
+
13
+ export function ServiceMonitorRenderer({ data }: ServiceMonitorRendererProps) {
14
+ const endpoints = getServiceMonitorEndpoints(data)
15
+ const selector = data.spec?.selector?.matchLabels
16
+ const conditions = data.status?.conditions
17
+
18
+ return (
19
+ <>
20
+ <Section title="ServiceMonitor" icon={Radio}>
21
+ <PropertyList>
22
+ <Property label="Job Label" value={getServiceMonitorJobLabel(data)} />
23
+ <Property label="Endpoints" value={String(endpoints.length)} />
24
+ <Property label="Namespace Scope" value={getServiceMonitorNamespaceSelector(data)} />
25
+ <Property label="Sample Limit" value={data.spec?.sampleLimit ? String(data.spec.sampleLimit) : undefined} />
26
+ </PropertyList>
27
+ </Section>
28
+
29
+ {endpoints.length > 0 && (
30
+ <Section title={`Scrape Endpoints (${endpoints.length})`} defaultExpanded>
31
+ <div className="space-y-2">
32
+ {endpoints.map((ep, i) => (
33
+ <div key={i} className="bg-theme-elevated/30 rounded p-2 text-sm">
34
+ <div className="flex items-center gap-2 flex-wrap">
35
+ {ep.port && (
36
+ <span className="text-theme-text-primary font-medium">{ep.port}</span>
37
+ )}
38
+ <span className="text-theme-text-secondary">{ep.path}</span>
39
+ {ep.interval && (
40
+ <span className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">
41
+ every {ep.interval}
42
+ </span>
43
+ )}
44
+ {ep.scheme && ep.scheme !== 'http' && (
45
+ <span className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary">
46
+ {ep.scheme}
47
+ </span>
48
+ )}
49
+ </div>
50
+ </div>
51
+ ))}
52
+ </div>
53
+ </Section>
54
+ )}
55
+
56
+ {selector && Object.keys(selector).length > 0 && (
57
+ <Section title="Selector">
58
+ <KeyValueBadgeList items={selector} />
59
+ </Section>
60
+ )}
61
+
62
+ <ConditionsSection conditions={conditions} />
63
+ </>
64
+ )
65
+ }
@@ -0,0 +1,98 @@
1
+ import { type ReactNode } from 'react'
2
+ import { Globe, Clock } from 'lucide-react'
3
+ import { Section, PropertyList, Property, KeyValueBadgeList, CopyHandler, AlertBanner } from '../../ui/drawer-components'
4
+
5
+ interface ServiceRendererProps {
6
+ data: any
7
+ onCopy: CopyHandler
8
+ copied: string | null
9
+ renderPortAction?: (props: { namespace: string; serviceName: string; port: number; protocol: string }) => ReactNode
10
+ }
11
+
12
+ export function ServiceRenderer({ data, onCopy, copied, renderPortAction }: ServiceRendererProps) {
13
+ const spec = data.spec || {}
14
+ const ports = spec.ports || []
15
+ const lbIngress = data.status?.loadBalancer?.ingress || []
16
+ const namespace = data.metadata?.namespace
17
+ const serviceName = data.metadata?.name
18
+
19
+ // Check for issues
20
+ const isLoadBalancer = spec.type === 'LoadBalancer'
21
+ const lbPending = isLoadBalancer && lbIngress.length === 0
22
+ const hasNoSelector = !spec.selector || Object.keys(spec.selector).length === 0
23
+ const isExternalName = spec.type === 'ExternalName'
24
+
25
+ return (
26
+ <>
27
+ {/* LoadBalancer pending warning */}
28
+ {lbPending && (
29
+ <AlertBanner
30
+ variant="warning"
31
+ icon={Clock}
32
+ title="Load Balancer Pending"
33
+ message="External IP/hostname has not been assigned yet. This may take a few minutes. Check Events below if provisioning is stuck."
34
+ />
35
+ )}
36
+
37
+ {/* No selector warning (manual endpoints) */}
38
+ {hasNoSelector && !isExternalName && (
39
+ <AlertBanner
40
+ variant="info"
41
+ title="No Pod Selector"
42
+ message="This service has no selector — endpoints must be managed manually or by an external controller."
43
+ />
44
+ )}
45
+
46
+ <Section title="Service" icon={Globe}>
47
+ <PropertyList>
48
+ <Property label="Type" value={spec.type || 'ClusterIP'} />
49
+ <Property label="Cluster IP" value={spec.clusterIP} copyable onCopy={onCopy} copied={copied} />
50
+ {spec.externalIPs?.length > 0 && (
51
+ <Property label="External IPs" value={spec.externalIPs.join(', ')} copyable onCopy={onCopy} copied={copied} />
52
+ )}
53
+ {lbIngress.length > 0 && (
54
+ <Property
55
+ label="Load Balancer"
56
+ value={lbIngress[0].ip || lbIngress[0].hostname}
57
+ copyable
58
+ onCopy={onCopy}
59
+ copied={copied}
60
+ />
61
+ )}
62
+ <Property label="Session Affinity" value={spec.sessionAffinity} />
63
+ <Property label="External Traffic" value={spec.externalTrafficPolicy} />
64
+ </PropertyList>
65
+ </Section>
66
+
67
+ <Section title="Ports" defaultExpanded>
68
+ <div className="space-y-2">
69
+ {ports.map((port: any, i: number) => (
70
+ <div key={`${port.port}-${port.protocol || 'TCP'}`} className="bg-theme-elevated/30 rounded p-2 text-sm">
71
+ <div className="flex items-center justify-between">
72
+ <span className="text-theme-text-primary font-medium">{port.name || `port-${i + 1}`}</span>
73
+ <div className="flex items-center gap-2">
74
+ {renderPortAction?.({
75
+ namespace,
76
+ serviceName,
77
+ port: port.port,
78
+ protocol: port.protocol || 'TCP',
79
+ })}
80
+ </div>
81
+ </div>
82
+ <div className="text-xs text-theme-text-secondary mt-1">
83
+ {port.port} {port.targetPort !== port.port && `→ ${port.targetPort}`}
84
+ {port.nodePort && ` (NodePort: ${port.nodePort})`}
85
+ </div>
86
+ </div>
87
+ ))}
88
+ </div>
89
+ </Section>
90
+
91
+ {spec.selector && (
92
+ <Section title="Selector">
93
+ <KeyValueBadgeList items={spec.selector} />
94
+ </Section>
95
+ )}
96
+ </>
97
+ )
98
+ }