@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,198 @@
1
+ // cert-manager CRD utility functions — extracted from resource-utils.ts
2
+
3
+ import type { StatusBadge, HealthLevel } from './resource-utils'
4
+ import { healthColors } from './resource-utils'
5
+
6
+ // ============================================================================
7
+ // CERTIFICATE UTILITIES (cert-manager CRD)
8
+ // ============================================================================
9
+
10
+ export function getCertificateStatus(cert: any): StatusBadge {
11
+ const conditions = cert.status?.conditions || []
12
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
13
+ if (readyCond?.status === 'True') {
14
+ return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
15
+ }
16
+ if (readyCond?.status === 'False') {
17
+ return { text: 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
18
+ }
19
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
20
+ }
21
+
22
+ export function getCertificateDomains(cert: any): string {
23
+ const dnsNames = cert.spec?.dnsNames || []
24
+ if (dnsNames.length === 0) return '-'
25
+ if (dnsNames.length === 1) return dnsNames[0]
26
+ if (dnsNames.length <= 2) return dnsNames.join(', ')
27
+ return `${dnsNames[0]} +${dnsNames.length - 1}`
28
+ }
29
+
30
+ export function getCertificateIssuer(cert: any): string {
31
+ const ref = cert.spec?.issuerRef
32
+ if (!ref) return '-'
33
+ return ref.name || '-'
34
+ }
35
+
36
+ export function getCertificateExpiry(cert: any): { text: string; level: HealthLevel } {
37
+ const notAfter = cert.status?.notAfter
38
+ if (!notAfter) return { text: '-', level: 'unknown' }
39
+
40
+ const expiryDate = new Date(notAfter)
41
+ const now = new Date()
42
+ const daysUntilExpiry = Math.floor((expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
43
+
44
+ if (daysUntilExpiry < 0) {
45
+ return { text: `Expired ${-daysUntilExpiry}d ago`, level: 'unhealthy' }
46
+ }
47
+ if (daysUntilExpiry < 7) {
48
+ return { text: `${daysUntilExpiry}d`, level: 'unhealthy' }
49
+ }
50
+ if (daysUntilExpiry < 30) {
51
+ return { text: `${daysUntilExpiry}d`, level: 'degraded' }
52
+ }
53
+ return { text: `${daysUntilExpiry}d`, level: 'healthy' }
54
+ }
55
+
56
+ // ============================================================================
57
+ // CERTIFICATE REQUEST UTILITIES (cert-manager)
58
+ // ============================================================================
59
+
60
+ export function getCertificateRequestStatus(cr: any): StatusBadge {
61
+ const conditions = cr.status?.conditions || []
62
+ const denied = conditions.find((c: any) => c.type === 'Denied' && c.status === 'True')
63
+ if (denied) return { text: 'Denied', color: healthColors.unhealthy, level: 'unhealthy' }
64
+
65
+ const ready = conditions.find((c: any) => c.type === 'Ready')
66
+ if (ready?.status === 'True') return { text: 'Issued', color: healthColors.healthy, level: 'healthy' }
67
+ if (ready?.status === 'False') return { text: ready.reason || 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
68
+
69
+ const approved = conditions.find((c: any) => c.type === 'Approved' && c.status === 'True')
70
+ if (approved) return { text: 'Approved', color: healthColors.degraded, level: 'degraded' }
71
+
72
+ return { text: 'Pending', color: healthColors.degraded, level: 'degraded' }
73
+ }
74
+
75
+ export function getCertificateRequestIssuer(cr: any): string {
76
+ const ref = cr.spec?.issuerRef
77
+ if (!ref) return '-'
78
+ return ref.name || '-'
79
+ }
80
+
81
+ export function getCertificateRequestApproved(cr: any): string {
82
+ const conditions = cr.status?.conditions || []
83
+ const approved = conditions.find((c: any) => c.type === 'Approved')
84
+ if (!approved) return 'Pending'
85
+ return approved.status === 'True' ? 'Yes' : 'No'
86
+ }
87
+
88
+ // ============================================================================
89
+ // CLUSTER ISSUER UTILITIES (cert-manager)
90
+ // ============================================================================
91
+
92
+ export function getClusterIssuerStatus(issuer: any): StatusBadge {
93
+ const conditions = issuer.status?.conditions || []
94
+ const ready = conditions.find((c: any) => c.type === 'Ready')
95
+ if (ready?.status === 'True') return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
96
+ if (ready?.status === 'False') return { text: ready.reason || 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
97
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
98
+ }
99
+
100
+ export function getClusterIssuerType(issuer: any): string {
101
+ const spec = issuer.spec || {}
102
+ if (spec.acme) return 'ACME'
103
+ if (spec.ca) return 'CA'
104
+ if (spec.selfSigned !== undefined) return 'SelfSigned'
105
+ if (spec.vault) return 'Vault'
106
+ if (spec.venafi) return 'Venafi'
107
+ return 'Unknown'
108
+ }
109
+
110
+ // ============================================================================
111
+ // ISSUER UTILITIES (cert-manager)
112
+ // Issuers and ClusterIssuers share the same spec/status schema in cert-manager,
113
+ // differing only in scope (namespaced vs cluster-wide).
114
+ // ============================================================================
115
+
116
+ export const getIssuerStatus = getClusterIssuerStatus
117
+ export const getIssuerType = getClusterIssuerType
118
+
119
+ // ============================================================================
120
+ // ORDER UTILITIES (cert-manager ACME)
121
+ // ============================================================================
122
+
123
+ export function getOrderState(order: any): StatusBadge {
124
+ const state = order.status?.state || ''
125
+ switch (state.toLowerCase()) {
126
+ case 'valid':
127
+ return { text: 'Valid', color: healthColors.healthy, level: 'healthy' }
128
+ case 'ready':
129
+ return { text: 'Ready', color: 'bg-blue-500/20 text-blue-400', level: 'healthy' }
130
+ case 'pending':
131
+ return { text: 'Pending', color: healthColors.degraded, level: 'degraded' }
132
+ case 'invalid':
133
+ return { text: 'Invalid', color: healthColors.unhealthy, level: 'unhealthy' }
134
+ case 'expired':
135
+ return { text: 'Expired', color: healthColors.unhealthy, level: 'unhealthy' }
136
+ case 'errored':
137
+ return { text: 'Errored', color: healthColors.unhealthy, level: 'unhealthy' }
138
+ default:
139
+ return { text: state || 'Unknown', color: healthColors.unknown, level: 'unknown' }
140
+ }
141
+ }
142
+
143
+ export function getOrderDomains(order: any): string {
144
+ const dnsNames = order.spec?.dnsNames || []
145
+ if (dnsNames.length === 0) return '-'
146
+ if (dnsNames.length === 1) return dnsNames[0]
147
+ if (dnsNames.length <= 2) return dnsNames.join(', ')
148
+ return `${dnsNames[0]} +${dnsNames.length - 1}`
149
+ }
150
+
151
+ export function getOrderIssuer(order: any): string {
152
+ return order.spec?.issuerRef?.name || '-'
153
+ }
154
+
155
+ // ============================================================================
156
+ // CHALLENGE UTILITIES (cert-manager ACME)
157
+ // ============================================================================
158
+
159
+ export function getChallengeState(challenge: any): StatusBadge {
160
+ const state = challenge.status?.state || ''
161
+ switch (state.toLowerCase()) {
162
+ case 'valid':
163
+ return { text: 'Valid', color: healthColors.healthy, level: 'healthy' }
164
+ case 'ready':
165
+ return { text: 'Ready', color: 'bg-blue-500/20 text-blue-400', level: 'healthy' }
166
+ case 'pending':
167
+ return { text: 'Pending', color: healthColors.degraded, level: 'degraded' }
168
+ case 'processing':
169
+ return { text: 'Processing', color: healthColors.degraded, level: 'degraded' }
170
+ case 'invalid':
171
+ return { text: 'Invalid', color: healthColors.unhealthy, level: 'unhealthy' }
172
+ case 'expired':
173
+ return { text: 'Expired', color: healthColors.unhealthy, level: 'unhealthy' }
174
+ case 'errored':
175
+ return { text: 'Errored', color: healthColors.unhealthy, level: 'unhealthy' }
176
+ default:
177
+ return { text: state || 'Unknown', color: healthColors.unknown, level: 'unknown' }
178
+ }
179
+ }
180
+
181
+ export function getChallengeType(challenge: any): string {
182
+ const type = challenge.spec?.type
183
+ if (type) return type
184
+ if (challenge.spec?.solver?.http01) return 'HTTP-01'
185
+ if (challenge.spec?.solver?.dns01) return 'DNS-01'
186
+ return 'Unknown'
187
+ }
188
+
189
+ export function getChallengeDomain(challenge: any): string {
190
+ return challenge.spec?.dnsName || '-'
191
+ }
192
+
193
+ export function getChallengePresented(challenge: any): string {
194
+ const presented = challenge.status?.presented
195
+ if (presented === true) return 'Yes'
196
+ if (presented === false) return 'No'
197
+ return '-'
198
+ }
@@ -0,0 +1,344 @@
1
+ // CloudNativePG CRD utility functions
2
+
3
+ import type { StatusBadge } from './resource-utils'
4
+ import { healthColors, formatAge, formatDuration } from './resource-utils'
5
+
6
+ // ============================================================================
7
+ // CNPG CLUSTER UTILITIES
8
+ // ============================================================================
9
+
10
+ export function getCNPGClusterStatus(resource: any): StatusBadge {
11
+ const status = resource.status || {}
12
+ const phase = status.phase || ''
13
+ const instances = resource.spec?.instances ?? 0
14
+ const readyInstances = status.readyInstances ?? 0
15
+
16
+ // Check for degraded instances first
17
+ if (instances > 0 && readyInstances < instances) {
18
+ // Distinguish between total failure and partial degradation
19
+ if (readyInstances === 0) {
20
+ return { text: 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
21
+ }
22
+ return { text: 'Degraded', color: healthColors.degraded, level: 'degraded' }
23
+ }
24
+
25
+ // Phase-based status
26
+ const healthyPhases = ['Cluster in healthy state', 'Healthy']
27
+ const transientPhases = [
28
+ 'Setting up primary',
29
+ 'Creating replica',
30
+ 'Switchover in progress',
31
+ 'Upgrading cluster',
32
+ 'Online upgrade in progress',
33
+ ]
34
+ const unhealthyPhases = [
35
+ 'Failing over',
36
+ 'Failing over (streaming)',
37
+ 'Failing over (designated primary)',
38
+ ]
39
+
40
+ if (healthyPhases.some(p => phase.includes(p))) {
41
+ return { text: 'Healthy', color: healthColors.healthy, level: 'healthy' }
42
+ }
43
+ if (unhealthyPhases.some(p => phase.includes(p))) {
44
+ return { text: 'Failing Over', color: healthColors.unhealthy, level: 'unhealthy' }
45
+ }
46
+ if (transientPhases.some(p => phase.includes(p))) {
47
+ return { text: phase, color: healthColors.degraded, level: 'degraded' }
48
+ }
49
+
50
+ // Conditions fallback
51
+ const conditions = status.conditions || []
52
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
53
+ if (readyCond?.status === 'True') {
54
+ return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
55
+ }
56
+ if (readyCond?.status === 'False') {
57
+ return { text: readyCond.reason || 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
58
+ }
59
+
60
+ if (phase) {
61
+ return { text: phase, color: healthColors.unknown, level: 'unknown' }
62
+ }
63
+
64
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
65
+ }
66
+
67
+ export function getCNPGClusterInstances(resource: any): string {
68
+ const desired = resource.spec?.instances ?? 0
69
+ const ready = resource.status?.readyInstances ?? 0
70
+ return `${ready}/${desired}`
71
+ }
72
+
73
+ export function getCNPGClusterPrimary(resource: any): string {
74
+ return resource.status?.currentPrimary || resource.status?.targetPrimary || '-'
75
+ }
76
+
77
+ export function getCNPGClusterPhase(resource: any): string {
78
+ return resource.status?.phase || '-'
79
+ }
80
+
81
+ export function getCNPGClusterImage(resource: any): string {
82
+ return resource.spec?.imageName || '-'
83
+ }
84
+
85
+ export function getCNPGClusterImageTag(resource: any): string {
86
+ const image = resource.spec?.imageName || ''
87
+ if (!image) return '-'
88
+ const parts = image.split(':')
89
+ if (parts.length > 1) return parts[parts.length - 1]
90
+ return image.split('/').pop() || '-'
91
+ }
92
+
93
+ export function getCNPGClusterStorage(resource: any): string {
94
+ return resource.spec?.storage?.size || '-'
95
+ }
96
+
97
+ export function getCNPGClusterStorageClass(resource: any): string {
98
+ return resource.spec?.storage?.storageClass || '-'
99
+ }
100
+
101
+ export function getCNPGClusterWALStorage(resource: any): { size?: string; storageClass?: string } | null {
102
+ const wal = resource.spec?.walStorage
103
+ if (!wal) return null
104
+ return { size: wal.size, storageClass: wal.storageClass }
105
+ }
106
+
107
+ export function getCNPGClusterBootstrapMethod(resource: any): string {
108
+ const bootstrap = resource.spec?.bootstrap
109
+ if (!bootstrap) return '-'
110
+ if (bootstrap.initdb) return 'initdb'
111
+ if (bootstrap.recovery) return 'recovery'
112
+ if (bootstrap.pg_basebackup) return 'pg_basebackup'
113
+ return '-'
114
+ }
115
+
116
+ export function getCNPGClusterUpdateStrategy(resource: any): string {
117
+ return resource.spec?.primaryUpdateStrategy || 'unsupervised'
118
+ }
119
+
120
+ export function getCNPGClusterBackupConfig(resource: any): {
121
+ configured: boolean
122
+ destinationPath?: string
123
+ retentionPolicy?: string
124
+ lastSuccessfulBackup?: string
125
+ firstRecoverabilityPoint?: string
126
+ } {
127
+ const backup = resource.spec?.backup
128
+ const barman = backup?.barmanObjectStore
129
+ return {
130
+ configured: !!barman,
131
+ destinationPath: barman?.destinationPath,
132
+ retentionPolicy: backup?.retentionPolicy,
133
+ lastSuccessfulBackup: resource.status?.lastSuccessfulBackup,
134
+ firstRecoverabilityPoint: resource.status?.firstRecoverabilityPoint,
135
+ }
136
+ }
137
+
138
+ export function getCNPGClusterMonitoring(resource: any): {
139
+ podMonitorEnabled: boolean
140
+ customQueriesConfigMap?: string[]
141
+ } {
142
+ const monitoring = resource.spec?.monitoring
143
+ return {
144
+ podMonitorEnabled: monitoring?.enablePodMonitor === true,
145
+ customQueriesConfigMap: monitoring?.customQueriesConfigMap?.map((ref: any) =>
146
+ typeof ref === 'string' ? ref : ref.name
147
+ ),
148
+ }
149
+ }
150
+
151
+ export function getCNPGClusterIsReplica(resource: any): boolean {
152
+ return !!resource.spec?.replicaCluster
153
+ }
154
+
155
+ export function getCNPGClusterReplicaSource(resource: any): string {
156
+ const replica = resource.spec?.replicaCluster
157
+ if (!replica) return '-'
158
+ return replica.source || replica.primary || '-'
159
+ }
160
+
161
+ export function getCNPGClusterInstanceNames(resource: any): string[] {
162
+ return resource.status?.instanceNames || []
163
+ }
164
+
165
+ export function getCNPGClusterPostgresParams(resource: any): Record<string, string> {
166
+ return resource.spec?.postgresql?.parameters || {}
167
+ }
168
+
169
+ // ============================================================================
170
+ // CNPG BACKUP UTILITIES
171
+ // ============================================================================
172
+
173
+ export function getCNPGBackupStatus(resource: any): StatusBadge {
174
+ const phase = resource.status?.phase || ''
175
+
176
+ switch (phase.toLowerCase()) {
177
+ case 'completed':
178
+ return { text: 'Completed', color: healthColors.healthy, level: 'healthy' }
179
+ case 'running':
180
+ return { text: 'Running', color: healthColors.degraded, level: 'degraded' }
181
+ case 'pending':
182
+ return { text: 'Pending', color: healthColors.degraded, level: 'degraded' }
183
+ case 'failed':
184
+ return { text: 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
185
+ default:
186
+ if (phase) return { text: phase, color: healthColors.unknown, level: 'unknown' }
187
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
188
+ }
189
+ }
190
+
191
+ export function getCNPGBackupCluster(resource: any): string {
192
+ return resource.spec?.cluster?.name || '-'
193
+ }
194
+
195
+ export function getCNPGBackupMethod(resource: any): string {
196
+ return resource.status?.method || resource.spec?.method || '-'
197
+ }
198
+
199
+ export function getCNPGBackupPhase(resource: any): string {
200
+ return resource.status?.phase || '-'
201
+ }
202
+
203
+ export function getCNPGBackupDuration(resource: any): string {
204
+ const started = resource.status?.startedAt
205
+ const stopped = resource.status?.stoppedAt
206
+ if (!started) return '-'
207
+ const startTime = new Date(started).getTime()
208
+ const endTime = stopped ? new Date(stopped).getTime() : Date.now()
209
+ return formatDuration(endTime - startTime, true)
210
+ }
211
+
212
+ export function getCNPGBackupStartedAt(resource: any): string {
213
+ return resource.status?.startedAt ? formatAge(resource.status.startedAt) : '-'
214
+ }
215
+
216
+ export function getCNPGBackupStoppedAt(resource: any): string {
217
+ return resource.status?.stoppedAt ? formatAge(resource.status.stoppedAt) : '-'
218
+ }
219
+
220
+ export function getCNPGBackupName(resource: any): string {
221
+ return resource.status?.backupName || '-'
222
+ }
223
+
224
+ export function getCNPGBackupDestinationPath(resource: any): string {
225
+ return resource.status?.destinationPath || '-'
226
+ }
227
+
228
+ export function getCNPGBackupServerName(resource: any): string {
229
+ return resource.status?.serverName || '-'
230
+ }
231
+
232
+ export function getCNPGBackupError(resource: any): string {
233
+ return resource.status?.error || ''
234
+ }
235
+
236
+ export function getCNPGBackupTarget(resource: any): string {
237
+ return resource.spec?.target || '-'
238
+ }
239
+
240
+ // ============================================================================
241
+ // CNPG SCHEDULED BACKUP UTILITIES
242
+ // ============================================================================
243
+
244
+ export function getCNPGScheduledBackupStatus(resource: any): StatusBadge {
245
+ const isSuspended = resource.spec?.suspend === true
246
+
247
+ if (isSuspended) {
248
+ return { text: 'Suspended', color: healthColors.degraded, level: 'degraded' }
249
+ }
250
+
251
+ // If we have a last schedule time, it's active
252
+ if (resource.status?.lastScheduleTime) {
253
+ return { text: 'Active', color: healthColors.healthy, level: 'healthy' }
254
+ }
255
+
256
+ // Check if immediate flag is set and no schedule has run yet
257
+ if (resource.spec?.immediate) {
258
+ return { text: 'Immediate', color: healthColors.healthy, level: 'healthy' }
259
+ }
260
+
261
+ return { text: 'Scheduled', color: healthColors.healthy, level: 'healthy' }
262
+ }
263
+
264
+ export function getCNPGScheduledBackupCluster(resource: any): string {
265
+ return resource.spec?.cluster?.name || '-'
266
+ }
267
+
268
+ export function getCNPGScheduleCron(resource: any): string {
269
+ return resource.spec?.schedule || '-'
270
+ }
271
+
272
+ export function getCNPGScheduledBackupMethod(resource: any): string {
273
+ return resource.spec?.method || 'barmanObjectStore'
274
+ }
275
+
276
+ export function getCNPGScheduledBackupLastSchedule(resource: any): string {
277
+ return resource.status?.lastScheduleTime ? formatAge(resource.status.lastScheduleTime) : '-'
278
+ }
279
+
280
+ export function getCNPGScheduledBackupNextSchedule(resource: any): string {
281
+ const next = resource.status?.nextScheduleTime
282
+ if (!next) return '-'
283
+ const nextDate = new Date(next)
284
+ const now = new Date()
285
+ const diffMs = nextDate.getTime() - now.getTime()
286
+ if (diffMs <= 0) return 'overdue'
287
+ return `in ${formatDuration(diffMs)}`
288
+ }
289
+
290
+ export function getCNPGScheduledBackupIsSuspended(resource: any): boolean {
291
+ return resource.spec?.suspend === true
292
+ }
293
+
294
+ export function getCNPGScheduledBackupIsImmediate(resource: any): boolean {
295
+ return resource.spec?.immediate === true
296
+ }
297
+
298
+ export function getCNPGScheduledBackupOwnerRef(resource: any): string {
299
+ return resource.spec?.backupOwnerReference || 'none'
300
+ }
301
+
302
+ // ============================================================================
303
+ // CNPG POOLER UTILITIES
304
+ // ============================================================================
305
+
306
+ export function getCNPGPoolerStatus(resource: any): StatusBadge {
307
+ const desired = resource.spec?.instances ?? 0
308
+ const ready = resource.status?.instances ?? 0
309
+
310
+ if (desired > 0 && ready < desired) {
311
+ if (ready === 0) {
312
+ return { text: 'Not Ready', color: healthColors.unhealthy, level: 'unhealthy' }
313
+ }
314
+ return { text: 'Degraded', color: healthColors.degraded, level: 'degraded' }
315
+ }
316
+
317
+ if (desired > 0 && ready >= desired) {
318
+ return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
319
+ }
320
+
321
+ return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
322
+ }
323
+
324
+ export function getCNPGPoolerCluster(resource: any): string {
325
+ return resource.spec?.cluster?.name || '-'
326
+ }
327
+
328
+ export function getCNPGPoolerType(resource: any): string {
329
+ return resource.spec?.type || '-'
330
+ }
331
+
332
+ export function getCNPGPoolerMode(resource: any): string {
333
+ return resource.spec?.pgbouncer?.poolMode || 'session'
334
+ }
335
+
336
+ export function getCNPGPoolerInstances(resource: any): string {
337
+ const desired = resource.spec?.instances ?? 0
338
+ const ready = resource.status?.instances ?? 0
339
+ return `${ready}/${desired}`
340
+ }
341
+
342
+ export function getCNPGPoolerParameters(resource: any): Record<string, string> {
343
+ return resource.spec?.pgbouncer?.parameters || {}
344
+ }