@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,194 @@
1
+ import { Database, HardDrive, Activity, Clock, Shield } from 'lucide-react'
2
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
+ import {
4
+ getCNPGClusterInstances,
5
+ getCNPGClusterPrimary,
6
+ getCNPGClusterPhase,
7
+ getCNPGClusterImage,
8
+ getCNPGClusterStorage,
9
+ getCNPGClusterStorageClass,
10
+ getCNPGClusterWALStorage,
11
+ getCNPGClusterBootstrapMethod,
12
+ getCNPGClusterUpdateStrategy,
13
+ getCNPGClusterBackupConfig,
14
+ getCNPGClusterMonitoring,
15
+ getCNPGClusterIsReplica,
16
+ getCNPGClusterReplicaSource,
17
+ getCNPGClusterInstanceNames,
18
+ getCNPGClusterPostgresParams,
19
+ } from '../resource-utils-cnpg'
20
+
21
+ interface CNPGClusterRendererProps {
22
+ data: any
23
+ onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
24
+ }
25
+
26
+ export function CNPGClusterRenderer({ data, onNavigate }: CNPGClusterRendererProps) {
27
+ const conditions = data.status?.conditions || []
28
+ const instances = data.spec?.instances ?? 0
29
+ const readyInstances = data.status?.readyInstances ?? 0
30
+ const phase = getCNPGClusterPhase(data)
31
+ const backupConfig = getCNPGClusterBackupConfig(data)
32
+ const monitoring = getCNPGClusterMonitoring(data)
33
+ const isReplica = getCNPGClusterIsReplica(data)
34
+ const walStorage = getCNPGClusterWALStorage(data)
35
+ const postgresParams = getCNPGClusterPostgresParams(data)
36
+ const instanceNames = getCNPGClusterInstanceNames(data)
37
+ const bootstrapMethod = getCNPGClusterBootstrapMethod(data)
38
+
39
+ // Problem detection
40
+ const isDegraded = instances > 0 && readyInstances < instances
41
+ const isFailover = phase.toLowerCase().includes('failing over')
42
+ const isSwitchover = phase.toLowerCase().includes('switchover')
43
+
44
+ return (
45
+ <>
46
+ {/* Problem alerts */}
47
+ {isDegraded && (
48
+ <AlertBanner
49
+ variant="warning"
50
+ title="Degraded Cluster"
51
+ message={`Only ${readyInstances} of ${instances} instances are ready.`}
52
+ />
53
+ )}
54
+ {isFailover && (
55
+ <AlertBanner
56
+ variant="error"
57
+ title="Failover in Progress"
58
+ message={`Cluster is performing a failover. Current phase: ${phase}`}
59
+ />
60
+ )}
61
+ {isSwitchover && (
62
+ <AlertBanner
63
+ variant="warning"
64
+ title="Switchover in Progress"
65
+ message={`Cluster is performing a switchover. Current phase: ${phase}`}
66
+ />
67
+ )}
68
+
69
+ {/* Cluster Overview */}
70
+ <Section title="Cluster Overview" icon={Database} defaultExpanded>
71
+ <PropertyList>
72
+ <Property label="Phase" value={phase} />
73
+ <Property label="Instances" value={getCNPGClusterInstances(data)} />
74
+ <Property label="Current Primary" value={getCNPGClusterPrimary(data)} />
75
+ <Property label="Image" value={getCNPGClusterImage(data)} />
76
+ <Property label="Update Strategy" value={getCNPGClusterUpdateStrategy(data)} />
77
+ </PropertyList>
78
+ {instanceNames.length > 0 && (
79
+ <div className="mt-2 pt-2 border-t border-theme-border">
80
+ <div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">Instance Nodes</div>
81
+ <div className="flex flex-wrap gap-1">
82
+ {instanceNames.map((name: string) => (
83
+ <span
84
+ key={name}
85
+ className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary font-mono"
86
+ >
87
+ {name}
88
+ </span>
89
+ ))}
90
+ </div>
91
+ </div>
92
+ )}
93
+ </Section>
94
+
95
+ {/* Storage */}
96
+ <Section title="Storage" icon={HardDrive} defaultExpanded>
97
+ <PropertyList>
98
+ <Property label="Data Size" value={getCNPGClusterStorage(data)} />
99
+ <Property label="Storage Class" value={getCNPGClusterStorageClass(data)} />
100
+ </PropertyList>
101
+ {walStorage && (
102
+ <div className="mt-2 pt-2 border-t border-theme-border">
103
+ <div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">WAL Storage</div>
104
+ <PropertyList>
105
+ {walStorage.size && <Property label="Size" value={walStorage.size} />}
106
+ {walStorage.storageClass && <Property label="Storage Class" value={walStorage.storageClass} />}
107
+ </PropertyList>
108
+ </div>
109
+ )}
110
+ </Section>
111
+
112
+ {/* Bootstrap - only show if method is interesting */}
113
+ {bootstrapMethod !== '-' && bootstrapMethod !== 'initdb' && (
114
+ <Section title="Bootstrap" icon={Activity} defaultExpanded>
115
+ <PropertyList>
116
+ <Property label="Method" value={bootstrapMethod} />
117
+ </PropertyList>
118
+ </Section>
119
+ )}
120
+
121
+ {/* Backup */}
122
+ {backupConfig.configured && (
123
+ <Section title="Backup" icon={Clock} defaultExpanded>
124
+ <PropertyList>
125
+ {backupConfig.destinationPath && (
126
+ <Property label="Destination" value={backupConfig.destinationPath} />
127
+ )}
128
+ {backupConfig.retentionPolicy && (
129
+ <Property label="Retention" value={backupConfig.retentionPolicy} />
130
+ )}
131
+ {backupConfig.lastSuccessfulBackup && (
132
+ <Property label="Last Successful" value={backupConfig.lastSuccessfulBackup} />
133
+ )}
134
+ {backupConfig.firstRecoverabilityPoint && (
135
+ <Property label="First Recoverability" value={backupConfig.firstRecoverabilityPoint} />
136
+ )}
137
+ </PropertyList>
138
+ </Section>
139
+ )}
140
+
141
+ {/* Monitoring */}
142
+ {(monitoring.podMonitorEnabled || (monitoring.customQueriesConfigMap && monitoring.customQueriesConfigMap.length > 0)) && (
143
+ <Section title="Monitoring" icon={Activity} defaultExpanded>
144
+ <PropertyList>
145
+ <Property label="Pod Monitor" value={monitoring.podMonitorEnabled ? 'Enabled' : 'Disabled'} />
146
+ </PropertyList>
147
+ {monitoring.customQueriesConfigMap && monitoring.customQueriesConfigMap.length > 0 && (
148
+ <div className="mt-2 pt-2 border-t border-theme-border">
149
+ <div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">Custom Queries</div>
150
+ <div className="flex flex-wrap gap-1">
151
+ {monitoring.customQueriesConfigMap.map((name: string) => (
152
+ <ResourceLink
153
+ key={name}
154
+ name={name}
155
+ kind="configmaps"
156
+ namespace={data.metadata?.namespace || ''}
157
+ onNavigate={onNavigate}
158
+ />
159
+ ))}
160
+ </div>
161
+ </div>
162
+ )}
163
+ </Section>
164
+ )}
165
+
166
+ {/* Replication - only if this is a replica cluster */}
167
+ {isReplica && (
168
+ <Section title="Replication" icon={Shield} defaultExpanded>
169
+ <PropertyList>
170
+ <Property label="Role" value="Replica" />
171
+ <Property label="Source" value={getCNPGClusterReplicaSource(data)} />
172
+ </PropertyList>
173
+ </Section>
174
+ )}
175
+
176
+ {/* PostgreSQL Parameters */}
177
+ {Object.keys(postgresParams).length > 0 && (
178
+ <Section title="PostgreSQL Parameters" defaultExpanded={false}>
179
+ <div className="space-y-0.5">
180
+ {Object.entries(postgresParams).map(([key, value]) => (
181
+ <div key={key} className="flex items-center gap-2 text-xs">
182
+ <span className="text-theme-text-secondary font-mono shrink-0">{key}</span>
183
+ <span className="text-theme-text-tertiary">=</span>
184
+ <span className="text-theme-text-primary font-mono break-all">{value}</span>
185
+ </div>
186
+ ))}
187
+ </div>
188
+ </Section>
189
+ )}
190
+
191
+ <ConditionsSection conditions={conditions} />
192
+ </>
193
+ )
194
+ }
@@ -0,0 +1,79 @@
1
+ import { Users, Database } from 'lucide-react'
2
+ import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
+ import {
4
+ getCNPGPoolerCluster,
5
+ getCNPGPoolerType,
6
+ getCNPGPoolerMode,
7
+ getCNPGPoolerInstances,
8
+ getCNPGPoolerParameters,
9
+ } from '../resource-utils-cnpg'
10
+
11
+ interface CNPGPoolerRendererProps {
12
+ data: any
13
+ onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
14
+ }
15
+
16
+ export function CNPGPoolerRenderer({ data, onNavigate }: CNPGPoolerRendererProps) {
17
+ const desired = data.spec?.instances ?? 0
18
+ const ready = data.status?.instances ?? 0
19
+ const isDegraded = desired > 0 && ready < desired
20
+ const clusterName = getCNPGPoolerCluster(data)
21
+ const parameters = getCNPGPoolerParameters(data)
22
+
23
+ return (
24
+ <>
25
+ {/* Problem alerts */}
26
+ {isDegraded && (
27
+ <AlertBanner
28
+ variant="warning"
29
+ title="Pooler Degraded"
30
+ message={`Only ${ready} of ${desired} pooler instances are ready.`}
31
+ />
32
+ )}
33
+
34
+ {/* Pooler Configuration */}
35
+ <Section title="Pooler Configuration" icon={Users} defaultExpanded>
36
+ <PropertyList>
37
+ <Property label="Type" value={getCNPGPoolerType(data)} />
38
+ <Property label="Pool Mode" value={getCNPGPoolerMode(data)} />
39
+ <Property label="Instances" value={getCNPGPoolerInstances(data)} />
40
+ </PropertyList>
41
+ </Section>
42
+
43
+ {/* Cluster Reference */}
44
+ <Section title="Cluster" icon={Database} defaultExpanded>
45
+ <PropertyList>
46
+ <Property label="Cluster" value={(() => {
47
+ if (clusterName && clusterName !== '-') {
48
+ return (
49
+ <ResourceLink
50
+ name={clusterName}
51
+ kind="clusters"
52
+ namespace={data.metadata?.namespace || ''}
53
+ group="postgresql.cnpg.io"
54
+ onNavigate={onNavigate}
55
+ />
56
+ )
57
+ }
58
+ return clusterName
59
+ })()} />
60
+ </PropertyList>
61
+ </Section>
62
+
63
+ {/* PgBouncer Parameters */}
64
+ {Object.keys(parameters).length > 0 && (
65
+ <Section title="PgBouncer Parameters" defaultExpanded>
66
+ <div className="space-y-0.5">
67
+ {Object.entries(parameters).map(([key, value]) => (
68
+ <div key={key} className="flex items-center gap-2 text-xs">
69
+ <span className="text-theme-text-secondary font-mono shrink-0">{key}</span>
70
+ <span className="text-theme-text-tertiary">=</span>
71
+ <span className="text-theme-text-primary font-mono break-all">{value}</span>
72
+ </div>
73
+ ))}
74
+ </div>
75
+ </Section>
76
+ )}
77
+ </>
78
+ )
79
+ }
@@ -0,0 +1,68 @@
1
+ import { Clock, Database } from 'lucide-react'
2
+ import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
3
+ import {
4
+ getCNPGScheduledBackupCluster,
5
+ getCNPGScheduleCron,
6
+ getCNPGScheduledBackupMethod,
7
+ getCNPGScheduledBackupLastSchedule,
8
+ getCNPGScheduledBackupNextSchedule,
9
+ getCNPGScheduledBackupIsSuspended,
10
+ getCNPGScheduledBackupIsImmediate,
11
+ getCNPGScheduledBackupOwnerRef,
12
+ } from '../resource-utils-cnpg'
13
+
14
+ interface CNPGScheduledBackupRendererProps {
15
+ data: any
16
+ onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
17
+ }
18
+
19
+ export function CNPGScheduledBackupRenderer({ data, onNavigate }: CNPGScheduledBackupRendererProps) {
20
+ const isSuspended = getCNPGScheduledBackupIsSuspended(data)
21
+ const clusterName = getCNPGScheduledBackupCluster(data)
22
+
23
+ return (
24
+ <>
25
+ {/* Suspended alert */}
26
+ {isSuspended && (
27
+ <AlertBanner
28
+ variant="warning"
29
+ title="Schedule Suspended"
30
+ message="This scheduled backup is currently suspended. No new backups will be created."
31
+ />
32
+ )}
33
+
34
+ {/* Schedule */}
35
+ <Section title="Schedule" icon={Clock} defaultExpanded>
36
+ <PropertyList>
37
+ <Property label="Cron Expression" value={getCNPGScheduleCron(data)} />
38
+ <Property label="Last Schedule" value={getCNPGScheduledBackupLastSchedule(data)} />
39
+ <Property label="Next Schedule" value={getCNPGScheduledBackupNextSchedule(data)} />
40
+ <Property label="Suspended" value={isSuspended ? 'Yes' : 'No'} />
41
+ <Property label="Immediate" value={getCNPGScheduledBackupIsImmediate(data) ? 'Yes' : 'No'} />
42
+ </PropertyList>
43
+ </Section>
44
+
45
+ {/* Backup Configuration */}
46
+ <Section title="Backup Configuration" icon={Database} defaultExpanded>
47
+ <PropertyList>
48
+ <Property label="Cluster" value={(() => {
49
+ if (clusterName && clusterName !== '-') {
50
+ return (
51
+ <ResourceLink
52
+ name={clusterName}
53
+ kind="clusters"
54
+ namespace={data.metadata?.namespace || ''}
55
+ group="postgresql.cnpg.io"
56
+ onNavigate={onNavigate}
57
+ />
58
+ )
59
+ }
60
+ return clusterName
61
+ })()} />
62
+ <Property label="Method" value={getCNPGScheduledBackupMethod(data)} />
63
+ <Property label="Owner Reference" value={getCNPGScheduledBackupOwnerRef(data)} />
64
+ </PropertyList>
65
+ </Section>
66
+ </>
67
+ )
68
+ }
@@ -0,0 +1,209 @@
1
+ import { Shield, Clock, Globe } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
4
+
5
+ interface CertificateRendererProps {
6
+ data: any
7
+ }
8
+
9
+ function formatDate(dateStr: string): string {
10
+ if (!dateStr) return '-'
11
+ const d = new Date(dateStr)
12
+ return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
13
+ }
14
+
15
+ function getDaysUntil(dateStr: string): number {
16
+ const target = new Date(dateStr).getTime()
17
+ const now = Date.now()
18
+ return Math.floor((target - now) / (1000 * 60 * 60 * 24))
19
+ }
20
+
21
+ export function CertificateRenderer({ data }: CertificateRendererProps) {
22
+ const spec = data.spec || {}
23
+ const status = data.status || {}
24
+ const conditions = status.conditions || []
25
+ const dnsNames = spec.dnsNames || []
26
+ const issuerRef = spec.issuerRef || {}
27
+ const usages = spec.usages || []
28
+
29
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
30
+ const isReady = readyCond?.status === 'True'
31
+ const isNotReady = readyCond?.status === 'False'
32
+
33
+ const notAfter = status.notAfter
34
+ const notBefore = status.notBefore
35
+ const renewalTime = status.renewalTime
36
+
37
+ const daysUntilExpiry = notAfter ? getDaysUntil(notAfter) : null
38
+ const isExpired = daysUntilExpiry !== null && daysUntilExpiry < 0
39
+ const expiresWithin7Days = daysUntilExpiry !== null && !isExpired && daysUntilExpiry <= 7
40
+ const expiresWithin30Days = daysUntilExpiry !== null && !isExpired && !expiresWithin7Days && daysUntilExpiry <= 30
41
+
42
+ // Progress bar calculation
43
+ let progressPct = 0
44
+ let progressColor = 'bg-green-500'
45
+ if (notBefore && notAfter) {
46
+ const now = Date.now()
47
+ const start = new Date(notBefore).getTime()
48
+ const end = new Date(notAfter).getTime()
49
+ progressPct = Math.min(100, Math.max(0, ((now - start) / (end - start)) * 100))
50
+ if (progressPct >= 90) {
51
+ progressColor = 'bg-red-500'
52
+ } else if (progressPct >= 70) {
53
+ progressColor = 'bg-yellow-500'
54
+ }
55
+ }
56
+
57
+ // Expiry remaining text color
58
+ const expiryTextColor = isExpired || expiresWithin7Days
59
+ ? 'text-red-400'
60
+ : expiresWithin30Days
61
+ ? 'text-yellow-400'
62
+ : 'text-green-400'
63
+
64
+ return (
65
+ <>
66
+ {/* Problem detection alerts */}
67
+ {isNotReady && (
68
+ <AlertBanner
69
+ variant="error"
70
+ title="Certificate Not Ready"
71
+ message={<>{readyCond.reason && <span className="font-medium">{readyCond.reason}: </span>}{readyCond.message || 'The certificate is not in a ready state.'}</>}
72
+ />
73
+ )}
74
+
75
+ {isExpired && (
76
+ <AlertBanner
77
+ variant="error"
78
+ title="Certificate has expired"
79
+ message={`Expired ${formatDate(notAfter)}. Renewal may be pending or failing.`}
80
+ />
81
+ )}
82
+
83
+ {expiresWithin7Days && (
84
+ <AlertBanner
85
+ variant="error"
86
+ title={`Certificate expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? 's' : ''}`}
87
+ message="Check that cert-manager is renewing this certificate."
88
+ />
89
+ )}
90
+
91
+ {expiresWithin30Days && (
92
+ <AlertBanner
93
+ variant="warning"
94
+ title={`Certificate expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? 's' : ''}`}
95
+ message="Renewal should happen automatically before expiry."
96
+ />
97
+ )}
98
+
99
+ {/* Certificate Info */}
100
+ <Section title="Certificate Info" icon={Shield}>
101
+ <PropertyList>
102
+ <Property
103
+ label="Status"
104
+ value={
105
+ <span className={clsx(
106
+ 'px-2 py-0.5 rounded text-xs font-medium',
107
+ isReady
108
+ ? 'bg-green-500/20 text-green-400'
109
+ : 'bg-red-500/20 text-red-400'
110
+ )}>
111
+ {isReady ? 'Ready' : 'Not Ready'}
112
+ </span>
113
+ }
114
+ />
115
+ <Property label="Secret Name" value={spec.secretName} />
116
+ <Property label="Revision" value={status.revision} />
117
+ </PropertyList>
118
+ </Section>
119
+
120
+ {/* Validity */}
121
+ <Section title="Validity" icon={Clock}>
122
+ <PropertyList>
123
+ <Property label="Not Before" value={notBefore ? formatDate(notBefore) : '-'} />
124
+ <Property
125
+ label="Not After"
126
+ value={
127
+ notAfter ? (
128
+ <span>
129
+ {formatDate(notAfter)}
130
+ {daysUntilExpiry !== null && (
131
+ <span className={clsx('ml-2 text-xs', expiryTextColor)}>
132
+ {isExpired
133
+ ? `(expired ${-daysUntilExpiry}d ago)`
134
+ : `(${daysUntilExpiry}d remaining)`}
135
+ </span>
136
+ )}
137
+ </span>
138
+ ) : '-'
139
+ }
140
+ />
141
+ <Property label="Renewal Time" value={renewalTime ? formatDate(renewalTime) : '-'} />
142
+ </PropertyList>
143
+
144
+ {/* Progress bar */}
145
+ {notBefore && notAfter && (
146
+ <div className="mt-3">
147
+ <div className="flex items-center justify-between text-xs text-theme-text-tertiary mb-1">
148
+ <span>{formatDate(notBefore)}</span>
149
+ <span>{formatDate(notAfter)}</span>
150
+ </div>
151
+ <div className="h-2 bg-theme-hover rounded overflow-hidden">
152
+ <div
153
+ className={clsx('h-full transition-all', progressColor)}
154
+ style={{ width: `${progressPct}%` }}
155
+ />
156
+ </div>
157
+ <div className="text-xs text-theme-text-tertiary mt-1 text-center">
158
+ {Math.round(progressPct)}% elapsed
159
+ </div>
160
+ </div>
161
+ )}
162
+ </Section>
163
+
164
+ {/* Domains */}
165
+ {dnsNames.length > 0 && (
166
+ <Section title="Domains" icon={Globe}>
167
+ <div className="flex flex-wrap gap-1">
168
+ {dnsNames.map((name: string) => (
169
+ <span
170
+ key={name}
171
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
172
+ >
173
+ {name}
174
+ </span>
175
+ ))}
176
+ </div>
177
+ </Section>
178
+ )}
179
+
180
+ {/* Issuer */}
181
+ <Section title="Issuer">
182
+ <PropertyList>
183
+ <Property label="Kind" value={issuerRef.kind} />
184
+ <Property label="Name" value={issuerRef.name} />
185
+ <Property label="Group" value={issuerRef.group} />
186
+ </PropertyList>
187
+ </Section>
188
+
189
+ {/* Usages */}
190
+ {usages.length > 0 && (
191
+ <Section title="Usages">
192
+ <div className="flex flex-wrap gap-1">
193
+ {usages.map((usage: string) => (
194
+ <span
195
+ key={usage}
196
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
197
+ >
198
+ {usage}
199
+ </span>
200
+ ))}
201
+ </div>
202
+ </Section>
203
+ )}
204
+
205
+ {/* Conditions */}
206
+ <ConditionsSection conditions={conditions} />
207
+ </>
208
+ )
209
+ }
@@ -0,0 +1,123 @@
1
+ import { Shield, FileText, Info } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
4
+
5
+ interface CertificateRequestRendererProps {
6
+ data: any
7
+ }
8
+
9
+ export function CertificateRequestRenderer({ data }: CertificateRequestRendererProps) {
10
+ const spec = data.spec || {}
11
+ const status = data.status || {}
12
+ const conditions = status.conditions || []
13
+ const issuerRef = spec.issuerRef || {}
14
+ const usages = spec.usages || []
15
+ const ownerRefs = data.metadata?.ownerReferences || []
16
+
17
+ const readyCond = conditions.find((c: any) => c.type === 'Ready')
18
+ const approvedCond = conditions.find((c: any) => c.type === 'Approved')
19
+ const deniedCond = conditions.find((c: any) => c.type === 'Denied')
20
+
21
+ const isReady = readyCond?.status === 'True'
22
+ const isNotReady = readyCond?.status === 'False'
23
+ const isApproved = approvedCond?.status === 'True'
24
+ const isDenied = deniedCond?.status === 'True'
25
+
26
+ const ownerCertificate = ownerRefs.find((ref: any) => ref.kind === 'Certificate')
27
+
28
+ return (
29
+ <>
30
+ {/* Problem detection alerts */}
31
+ {isNotReady && (
32
+ <AlertBanner
33
+ variant="error"
34
+ title="Certificate Request Not Ready"
35
+ message={<>{readyCond.reason && <span className="font-medium">{readyCond.reason}: </span>}{readyCond.message || 'The certificate request is not in a ready state.'}</>}
36
+ />
37
+ )}
38
+
39
+ {isDenied && (
40
+ <AlertBanner
41
+ variant="error"
42
+ title="Certificate request was denied"
43
+ message={<>{deniedCond.reason && <span className="font-medium">{deniedCond.reason}: </span>}{deniedCond.message || 'The certificate request has been denied by the approver.'}</>}
44
+ />
45
+ )}
46
+
47
+ {/* Status */}
48
+ <Section title="Status" icon={Shield}>
49
+ <PropertyList>
50
+ <Property
51
+ label="Ready"
52
+ value={
53
+ <span className={clsx(
54
+ 'px-2 py-0.5 rounded text-xs font-medium',
55
+ isReady
56
+ ? 'bg-green-500/20 text-green-400'
57
+ : 'bg-red-500/20 text-red-400'
58
+ )}>
59
+ {isReady ? 'Ready' : 'Not Ready'}
60
+ </span>
61
+ }
62
+ />
63
+ <Property
64
+ label="Approved"
65
+ value={
66
+ <span className={clsx(
67
+ 'px-2 py-0.5 rounded text-xs font-medium',
68
+ isApproved
69
+ ? 'bg-green-500/20 text-green-400'
70
+ : isDenied
71
+ ? 'bg-red-500/20 text-red-400'
72
+ : 'bg-yellow-500/20 text-yellow-400'
73
+ )}>
74
+ {isApproved ? 'Yes' : isDenied ? 'No' : 'Pending'}
75
+ </span>
76
+ }
77
+ />
78
+ <Property label="Reason" value={readyCond?.reason} />
79
+ {ownerCertificate && (
80
+ <Property label="Owner Certificate" value={ownerCertificate.name} />
81
+ )}
82
+ </PropertyList>
83
+ </Section>
84
+
85
+ {/* Issuer */}
86
+ <Section title="Issuer" icon={Info}>
87
+ <PropertyList>
88
+ <Property label="Kind" value={issuerRef.kind} />
89
+ <Property label="Name" value={issuerRef.name} />
90
+ <Property label="Group" value={issuerRef.group} />
91
+ </PropertyList>
92
+ </Section>
93
+
94
+ {/* Request Details */}
95
+ <Section title="Request Details" icon={FileText}>
96
+ <PropertyList>
97
+ <Property label="Duration" value={spec.duration} />
98
+ {usages.length > 0 && (
99
+ <Property
100
+ label="Usages"
101
+ value={
102
+ <div className="flex flex-wrap gap-1">
103
+ {usages.map((usage: string) => (
104
+ <span
105
+ key={usage}
106
+ className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
107
+ >
108
+ {usage}
109
+ </span>
110
+ ))}
111
+ </div>
112
+ }
113
+ />
114
+ )}
115
+ <Property label="Has Certificate" value={status.certificate ? 'Yes' : 'No'} />
116
+ </PropertyList>
117
+ </Section>
118
+
119
+ {/* Conditions */}
120
+ <ConditionsSection conditions={conditions} />
121
+ </>
122
+ )
123
+ }