@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,385 @@
1
+ // External Secrets Operator CRD utility functions
2
+
3
+ import type { StatusBadge } from './resource-utils'
4
+ import { healthColors, formatAge } from './resource-utils'
5
+
6
+ // ============================================================================
7
+ // PROVIDER DETECTION
8
+ // ============================================================================
9
+
10
+ const PROVIDER_MAP: Record<string, string> = {
11
+ aws: 'AWS Secrets Manager',
12
+ azurekv: 'Azure Key Vault',
13
+ gcpsm: 'GCP Secret Manager',
14
+ vault: 'HashiCorp Vault',
15
+ kubernetes: 'Kubernetes',
16
+ oracle: 'Oracle Vault',
17
+ ibm: 'IBM Secrets Manager',
18
+ doppler: 'Doppler',
19
+ onepassword: '1Password',
20
+ senhasegura: 'senhasegura',
21
+ gitlab: 'GitLab',
22
+ webhook: 'Webhook',
23
+ fake: 'Fake',
24
+ keepersecurity: 'Keeper Security',
25
+ scaleway: 'Scaleway',
26
+ delinea: 'Delinea',
27
+ chef: 'Chef',
28
+ pulumi: 'Pulumi ESC',
29
+ fortanix: 'Fortanix',
30
+ passworddepot: 'Password Depot',
31
+ device42: 'Device42',
32
+ akeyless: 'Akeyless',
33
+ beyondtrust: 'BeyondTrust',
34
+ infisical: 'Infisical',
35
+ passbolt: 'Passbolt',
36
+ bitwarden: 'Bitwarden',
37
+ }
38
+
39
+ /**
40
+ * Detect the provider type from a SecretStore/ClusterSecretStore spec.
41
+ * Checks which provider key exists in spec.provider.
42
+ */
43
+ export function getSecretStoreProviderType(resource: any): string {
44
+ const provider = resource.spec?.provider
45
+ if (!provider) return 'Unknown'
46
+
47
+ for (const [key, label] of Object.entries(PROVIDER_MAP)) {
48
+ if (provider[key]) return label
49
+ }
50
+
51
+ // Fallback: return the first key found in provider
52
+ const keys = Object.keys(provider).filter(k => k !== 'retrySettings' && k !== 'controller')
53
+ if (keys.length > 0) return keys[0]
54
+
55
+ return 'Unknown'
56
+ }
57
+
58
+ /**
59
+ * Get the short provider key (e.g., 'aws', 'vault') for color mapping.
60
+ */
61
+ export function getSecretStoreProviderKey(resource: any): string {
62
+ const provider = resource.spec?.provider
63
+ if (!provider) return 'unknown'
64
+
65
+ for (const key of Object.keys(PROVIDER_MAP)) {
66
+ if (provider[key]) return key
67
+ }
68
+
69
+ return 'unknown'
70
+ }
71
+
72
+ /**
73
+ * Get provider-specific details from a SecretStore/ClusterSecretStore.
74
+ * Returns key-value pairs suitable for display, never exposing secret values.
75
+ */
76
+ export function getSecretStoreProviderDetails(resource: any): Array<{ label: string; value: string }> {
77
+ const provider = resource.spec?.provider
78
+ if (!provider) return []
79
+
80
+ const details: Array<{ label: string; value: string }> = []
81
+
82
+ // AWS
83
+ if (provider.aws) {
84
+ const aws = provider.aws
85
+ if (aws.region) details.push({ label: 'Region', value: aws.region })
86
+ if (aws.service) details.push({ label: 'Service', value: aws.service })
87
+ if (aws.role) details.push({ label: 'Role ARN', value: aws.role })
88
+ if (aws.auth?.jwt?.serviceAccountRef?.name) {
89
+ details.push({ label: 'Service Account', value: aws.auth.jwt.serviceAccountRef.name })
90
+ }
91
+ }
92
+
93
+ // Azure Key Vault
94
+ if (provider.azurekv) {
95
+ const az = provider.azurekv
96
+ if (az.vaultUrl) details.push({ label: 'Vault URL', value: az.vaultUrl })
97
+ if (az.tenantId) details.push({ label: 'Tenant ID', value: az.tenantId })
98
+ if (az.authType) details.push({ label: 'Auth Type', value: az.authType })
99
+ if (az.environmentType) details.push({ label: 'Environment', value: az.environmentType })
100
+ }
101
+
102
+ // GCP Secret Manager
103
+ if (provider.gcpsm) {
104
+ const gcp = provider.gcpsm
105
+ if (gcp.projectID) details.push({ label: 'Project ID', value: gcp.projectID })
106
+ if (gcp.location) details.push({ label: 'Location', value: gcp.location })
107
+ }
108
+
109
+ // HashiCorp Vault
110
+ if (provider.vault) {
111
+ const vault = provider.vault
112
+ if (vault.server) details.push({ label: 'Server', value: vault.server })
113
+ if (vault.path) details.push({ label: 'Path', value: vault.path })
114
+ if (vault.version) details.push({ label: 'Version', value: vault.version })
115
+ if (vault.namespace) details.push({ label: 'Namespace', value: vault.namespace })
116
+ // Auth method detection
117
+ const auth = vault.auth
118
+ if (auth) {
119
+ if (auth.kubernetes) details.push({ label: 'Auth Method', value: 'Kubernetes' })
120
+ else if (auth.appRole) details.push({ label: 'Auth Method', value: 'AppRole' })
121
+ else if (auth.tokenSecretRef) details.push({ label: 'Auth Method', value: 'Token' })
122
+ else if (auth.jwt) details.push({ label: 'Auth Method', value: 'JWT' })
123
+ else if (auth.ldap) details.push({ label: 'Auth Method', value: 'LDAP' })
124
+ else if (auth.userPass) details.push({ label: 'Auth Method', value: 'UserPass' })
125
+ else if (auth.cert) details.push({ label: 'Auth Method', value: 'Certificate' })
126
+ else if (auth.iam) details.push({ label: 'Auth Method', value: 'IAM' })
127
+ }
128
+ }
129
+
130
+ // Kubernetes
131
+ if (provider.kubernetes) {
132
+ const k8s = provider.kubernetes
133
+ if (k8s.server?.url) {
134
+ details.push({ label: 'Server', value: k8s.server.url })
135
+ } else {
136
+ details.push({ label: 'Server', value: 'In-cluster' })
137
+ }
138
+ if (k8s.remoteNamespace) details.push({ label: 'Remote Namespace', value: k8s.remoteNamespace })
139
+ }
140
+
141
+ // Oracle Vault
142
+ if (provider.oracle) {
143
+ const oracle = provider.oracle
144
+ if (oracle.vault) details.push({ label: 'Vault OCID', value: oracle.vault })
145
+ if (oracle.region) details.push({ label: 'Region', value: oracle.region })
146
+ }
147
+
148
+ // IBM
149
+ if (provider.ibm) {
150
+ const ibm = provider.ibm
151
+ if (ibm.serviceUrl) details.push({ label: 'Service URL', value: ibm.serviceUrl })
152
+ }
153
+
154
+ // Doppler
155
+ if (provider.doppler) {
156
+ const doppler = provider.doppler
157
+ if (doppler.project) details.push({ label: 'Project', value: doppler.project })
158
+ if (doppler.config) details.push({ label: 'Config', value: doppler.config })
159
+ }
160
+
161
+ // 1Password
162
+ if (provider.onepassword) {
163
+ const op = provider.onepassword
164
+ if (op.connectHost) details.push({ label: 'Connect Host', value: op.connectHost })
165
+ if (op.vaults) {
166
+ const vaultNames = Object.keys(op.vaults)
167
+ if (vaultNames.length > 0) details.push({ label: 'Vaults', value: vaultNames.join(', ') })
168
+ }
169
+ }
170
+
171
+ // Akeyless
172
+ if (provider.akeyless) {
173
+ const ak = provider.akeyless
174
+ if (ak.akeylessGWApiURL) details.push({ label: 'Gateway URL', value: ak.akeylessGWApiURL })
175
+ }
176
+
177
+ return details
178
+ }
179
+
180
+ // ============================================================================
181
+ // EXTERNALSECRET UTILITIES
182
+ // ============================================================================
183
+
184
+ export function getExternalSecretStatus(resource: any): StatusBadge {
185
+ const conditions = resource.status?.conditions || []
186
+
187
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
188
+ if (readyCond?.status === 'True') {
189
+ return { text: 'Synced', color: healthColors.healthy, level: 'healthy' }
190
+ }
191
+ if (readyCond?.status === 'False') {
192
+ return { text: readyCond.reason || 'Not Synced', color: healthColors.unhealthy, level: 'unhealthy' }
193
+ }
194
+
195
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
196
+ }
197
+
198
+ export function getExternalSecretStore(resource: any): { name: string; kind: string } {
199
+ const ref = resource.spec?.secretStoreRef
200
+ return {
201
+ name: ref?.name || '-',
202
+ kind: ref?.kind || 'SecretStore',
203
+ }
204
+ }
205
+
206
+ export function getExternalSecretRefreshInterval(resource: any): string {
207
+ return resource.spec?.refreshInterval || '-'
208
+ }
209
+
210
+ export function getExternalSecretSecretCount(resource: any): number {
211
+ const dataCount = (resource.spec?.data || []).length
212
+ const dataFromCount = (resource.spec?.dataFrom || []).length
213
+ return dataCount + dataFromCount
214
+ }
215
+
216
+ export function getExternalSecretTargetName(resource: any): string {
217
+ return resource.spec?.target?.name || resource.metadata?.name || '-'
218
+ }
219
+
220
+ export function getExternalSecretTargetCreationPolicy(resource: any): string {
221
+ return resource.spec?.target?.creationPolicy || 'Owner'
222
+ }
223
+
224
+ export function getExternalSecretTargetDeletionPolicy(resource: any): string {
225
+ return resource.spec?.target?.deletionPolicy || 'Retain'
226
+ }
227
+
228
+ export function getExternalSecretLastSync(resource: any): string {
229
+ const refreshTime = resource.status?.refreshTime
230
+ if (!refreshTime) return '-'
231
+ return formatAge(refreshTime)
232
+ }
233
+
234
+ export function getExternalSecretDataMappings(resource: any): Array<{
235
+ secretKey: string
236
+ remoteKey: string
237
+ remoteProperty?: string
238
+ remoteVersion?: string
239
+ }> {
240
+ const data = resource.spec?.data || []
241
+ return data.map((d: any) => ({
242
+ secretKey: d.secretKey || '',
243
+ remoteKey: d.remoteRef?.key || '',
244
+ remoteProperty: d.remoteRef?.property,
245
+ remoteVersion: d.remoteRef?.version,
246
+ }))
247
+ }
248
+
249
+ export function getExternalSecretDataFromSources(resource: any): Array<{
250
+ type: string
251
+ details: string
252
+ }> {
253
+ const dataFrom = resource.spec?.dataFrom || []
254
+ return dataFrom.map((df: any) => {
255
+ if (df.extract) {
256
+ return { type: 'extract', details: df.extract.key || '' }
257
+ }
258
+ if (df.find) {
259
+ return { type: 'find', details: df.find.name?.regexp || df.find.tags ? 'by tags' : 'by name' }
260
+ }
261
+ if (df.sourceRef) {
262
+ return { type: 'sourceRef', details: `${df.sourceRef.kind || ''}/${df.sourceRef.name || ''}` }
263
+ }
264
+ return { type: 'unknown', details: '' }
265
+ })
266
+ }
267
+
268
+ /**
269
+ * Get the provider type from an ExternalSecret by reading the secretStoreRef
270
+ * and resolving via the store's spec. Since we don't have the store data here,
271
+ * this is a best-effort based on available info.
272
+ */
273
+ export function getExternalSecretProvider(resource: any): string {
274
+ // ExternalSecrets don't directly contain provider info.
275
+ // The provider is on the SecretStore. Return the store name as a hint.
276
+ const store = getExternalSecretStore(resource)
277
+ return store.name !== '-' ? store.name : '-'
278
+ }
279
+
280
+ // ============================================================================
281
+ // CLUSTEREXTERNALSECRET UTILITIES
282
+ // ============================================================================
283
+
284
+ export function getClusterExternalSecretStatus(resource: any): StatusBadge {
285
+ const conditions = resource.status?.conditions || []
286
+ const failedNamespaces = resource.status?.failedNamespaces || []
287
+
288
+ if (failedNamespaces.length > 0) {
289
+ return { text: 'Partial Failure', color: healthColors.unhealthy, level: 'unhealthy' }
290
+ }
291
+
292
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
293
+ if (readyCond?.status === 'True') {
294
+ return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
295
+ }
296
+ if (readyCond?.status === 'False') {
297
+ return { text: readyCond.reason || 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
298
+ }
299
+
300
+ // Check for PartiallyReady or similar
301
+ const partialCond = conditions.find((c: any) => c.type === 'PartiallyReady')
302
+ if (partialCond?.status === 'True') {
303
+ return { text: 'Partial', color: healthColors.degraded, level: 'degraded' }
304
+ }
305
+
306
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
307
+ }
308
+
309
+ export function getClusterExternalSecretNamespaceCount(resource: any): number {
310
+ const provisioned = resource.status?.provisionedNamespaces || []
311
+ return provisioned.length
312
+ }
313
+
314
+ export function getClusterExternalSecretFailedCount(resource: any): number {
315
+ const failed = resource.status?.failedNamespaces || []
316
+ // failedNamespaces can be an array of strings or objects with namespace+reason
317
+ return failed.length
318
+ }
319
+
320
+ export function getClusterExternalSecretNamespaces(resource: any): string[] {
321
+ // From spec: either namespaceSelector or explicit namespaces
322
+ return resource.spec?.namespaces || []
323
+ }
324
+
325
+ export function getClusterExternalSecretNamespaceSelector(resource: any): Record<string, string> | null {
326
+ return resource.spec?.namespaceSelector?.matchLabels || null
327
+ }
328
+
329
+ export function getClusterExternalSecretProvisionedNamespaces(resource: any): string[] {
330
+ return resource.status?.provisionedNamespaces || []
331
+ }
332
+
333
+ export function getClusterExternalSecretFailedNamespaces(resource: any): Array<{
334
+ namespace: string
335
+ reason?: string
336
+ }> {
337
+ const failed = resource.status?.failedNamespaces || []
338
+ return failed.map((f: any) => {
339
+ if (typeof f === 'string') return { namespace: f }
340
+ return { namespace: f.namespace || f.name || '', reason: f.reason || f.message }
341
+ })
342
+ }
343
+
344
+ // ============================================================================
345
+ // SECRETSTORE UTILITIES
346
+ // ============================================================================
347
+
348
+ export function getSecretStoreStatus(resource: any): StatusBadge {
349
+ const conditions = resource.status?.conditions || []
350
+
351
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
352
+ if (readyCond?.status === 'True') {
353
+ return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
354
+ }
355
+ if (readyCond?.status === 'False') {
356
+ return { text: readyCond.reason || 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
357
+ }
358
+
359
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
360
+ }
361
+
362
+ export function getSecretStoreRetrySettings(resource: any): {
363
+ maxRetries?: number
364
+ retryInterval?: string
365
+ } | null {
366
+ const retry = resource.spec?.retrySettings
367
+ if (!retry) return null
368
+ return {
369
+ maxRetries: retry.maxRetries,
370
+ retryInterval: retry.retryInterval,
371
+ }
372
+ }
373
+
374
+ export function getSecretStoreController(resource: any): string | null {
375
+ return resource.spec?.controller || null
376
+ }
377
+
378
+ // ============================================================================
379
+ // CLUSTERSECRETSTORE UTILITIES
380
+ // ============================================================================
381
+
382
+ // ClusterSecretStore uses the same structure as SecretStore
383
+ export function getClusterSecretStoreStatus(resource: any): StatusBadge {
384
+ return getSecretStoreStatus(resource)
385
+ }
@@ -0,0 +1,186 @@
1
+ // FluxCD CRD utility functions — extracted from resource-utils.ts
2
+
3
+ import type { StatusBadge } from './resource-utils'
4
+ import { healthColors } from './resource-utils'
5
+ import type { FluxCondition } from '../../types/gitops'
6
+
7
+ // ============================================================================
8
+ // FLUXCD STATUS UTILITIES
9
+ // ============================================================================
10
+
11
+ /**
12
+ * Generic status function for FluxCD resources that follow the standard Ready condition pattern.
13
+ * Works for: GitRepository, OCIRepository, HelmRepository, Alert
14
+ */
15
+ export function getFluxResourceStatus(resource: any): StatusBadge {
16
+ const conditions: FluxCondition[] = resource.status?.conditions || []
17
+ const readyCondition = conditions.find((c) => c.type === 'Ready')
18
+
19
+ if (resource.spec?.suspend) {
20
+ return { text: 'Suspended', color: healthColors.degraded, level: 'degraded' }
21
+ }
22
+ if (readyCondition?.status === 'True') {
23
+ return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
24
+ }
25
+ if (readyCondition?.status === 'False') {
26
+ return { text: 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
27
+ }
28
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
29
+ }
30
+
31
+ // Aliases for specific resource types (all use the same logic)
32
+ export const getGitRepositoryStatus = getFluxResourceStatus
33
+ export const getOCIRepositoryStatus = getFluxResourceStatus
34
+ export const getHelmRepositoryStatus = getFluxResourceStatus
35
+ export const getFluxAlertStatus = getFluxResourceStatus
36
+
37
+ /**
38
+ * Kustomization has additional Healthy condition check
39
+ */
40
+ export function getKustomizationStatus(ks: any): StatusBadge {
41
+ const conditions: FluxCondition[] = ks.status?.conditions || []
42
+ const readyCondition = conditions.find((c) => c.type === 'Ready')
43
+ const healthyCondition = conditions.find((c) => c.type === 'Healthy')
44
+
45
+ if (ks.spec?.suspend) {
46
+ return { text: 'Suspended', color: healthColors.degraded, level: 'degraded' }
47
+ }
48
+ if (readyCondition?.status === 'True') {
49
+ // Check health condition for more nuanced status
50
+ if (healthyCondition?.status === 'False') {
51
+ return { text: 'Unhealthy', color: healthColors.degraded, level: 'degraded' }
52
+ }
53
+ return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
54
+ }
55
+ if (readyCondition?.status === 'False') {
56
+ return { text: 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
57
+ }
58
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
59
+ }
60
+
61
+ /**
62
+ * HelmRelease has Released condition and remediation detection
63
+ */
64
+ export function getFluxHelmReleaseStatus(hr: any): StatusBadge {
65
+ const conditions: FluxCondition[] = hr.status?.conditions || []
66
+ const readyCondition = conditions.find((c) => c.type === 'Ready')
67
+ const releasedCondition = conditions.find((c) => c.type === 'Released')
68
+
69
+ if (hr.spec?.suspend) {
70
+ return { text: 'Suspended', color: healthColors.degraded, level: 'degraded' }
71
+ }
72
+ if (readyCondition?.status === 'True') {
73
+ return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
74
+ }
75
+ if (readyCondition?.status === 'False') {
76
+ // Check if it's a remediation in progress
77
+ const reason = readyCondition?.reason || ''
78
+ if (reason.includes('Remediation') || reason.includes('Retry')) {
79
+ return { text: 'Remediating', color: healthColors.degraded, level: 'degraded' }
80
+ }
81
+ return { text: 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
82
+ }
83
+ if (releasedCondition?.status === 'True') {
84
+ return { text: 'Released', color: healthColors.healthy, level: 'healthy' }
85
+ }
86
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
87
+ }
88
+
89
+ // ============================================================================
90
+ // FLUXCD TABLE CELL UTILITIES
91
+ // ============================================================================
92
+
93
+ export function getGitRepositoryUrl(repo: any): string {
94
+ return repo.spec?.url || '-'
95
+ }
96
+
97
+ export function getGitRepositoryRef(repo: any): string {
98
+ const ref = repo.spec?.ref
99
+ if (!ref) return '-'
100
+ if (ref.branch) return ref.branch
101
+ if (ref.tag) return ref.tag
102
+ if (ref.semver) return ref.semver
103
+ if (ref.commit) return ref.commit.substring(0, 7)
104
+ return '-'
105
+ }
106
+
107
+ export function getGitRepositoryRevision(repo: any): string {
108
+ const artifact = repo.status?.artifact
109
+ if (!artifact?.revision) return '-'
110
+ // Format: "branch@sha1:abc123..." or just "sha1:abc123"
111
+ const rev = artifact.revision
112
+ const shaMatch = rev.match(/sha1:([a-f0-9]+)/)
113
+ if (shaMatch) return shaMatch[1].substring(0, 7)
114
+ return rev.substring(0, 12)
115
+ }
116
+
117
+ export function getOCIRepositoryUrl(repo: any): string {
118
+ return repo.spec?.url || '-'
119
+ }
120
+
121
+ export function getOCIRepositoryRef(repo: any): string {
122
+ const ref = repo.spec?.ref
123
+ if (!ref) return '-'
124
+ if (ref.tag) return ref.tag
125
+ if (ref.semver) return ref.semver
126
+ if (ref.digest) return ref.digest.substring(0, 12)
127
+ return '-'
128
+ }
129
+
130
+ export function getOCIRepositoryRevision(repo: any): string {
131
+ const artifact = repo.status?.artifact
132
+ if (!artifact?.revision) return '-'
133
+ // Usually a digest like "sha256:abc123..."
134
+ const rev = artifact.revision
135
+ if (rev.startsWith('sha256:')) return rev.substring(7, 19)
136
+ return rev.substring(0, 12)
137
+ }
138
+
139
+ export function getHelmRepositoryUrl(repo: any): string {
140
+ return repo.spec?.url || '-'
141
+ }
142
+
143
+ export function getHelmRepositoryType(repo: any): string {
144
+ return repo.spec?.type || 'default'
145
+ }
146
+
147
+ export function getKustomizationSource(ks: any): string {
148
+ const ref = ks.spec?.sourceRef
149
+ if (!ref) return '-'
150
+ return `${ref.kind}/${ref.name}`
151
+ }
152
+
153
+ export function getKustomizationPath(ks: any): string {
154
+ return ks.spec?.path || './'
155
+ }
156
+
157
+ export function getKustomizationInventory(ks: any): number {
158
+ return ks.status?.inventory?.entries?.length || 0
159
+ }
160
+
161
+ export function getFluxHelmReleaseChart(hr: any): string {
162
+ const chart = hr.spec?.chart?.spec
163
+ if (!chart) return '-'
164
+ return chart.chart || '-'
165
+ }
166
+
167
+ export function getFluxHelmReleaseVersion(hr: any): string {
168
+ const chart = hr.spec?.chart?.spec
169
+ if (!chart?.version) return '*'
170
+ return chart.version
171
+ }
172
+
173
+ export function getFluxHelmReleaseRevision(hr: any): number {
174
+ // Helm release revision number is in history[0].version
175
+ return hr.status?.history?.[0]?.version || 0
176
+ }
177
+
178
+ export function getFluxAlertProvider(alert: any): string {
179
+ const ref = alert.spec?.providerRef
180
+ if (!ref) return '-'
181
+ return ref.name || '-'
182
+ }
183
+
184
+ export function getFluxAlertEventCount(alert: any): number {
185
+ return alert.spec?.eventSources?.length || 0
186
+ }