@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,458 @@
1
+ // GitOps unified types and mappers for FluxCD and ArgoCD
2
+
3
+ // ============================================================================
4
+ // OPERATION RESPONSE TYPES
5
+ // ============================================================================
6
+
7
+ /** GitOps tool identifier */
8
+ export type GitOpsTool = 'argocd' | 'fluxcd'
9
+
10
+ /** GitOps operation types */
11
+ export type GitOpsOperation = 'sync' | 'refresh' | 'terminate' | 'suspend' | 'resume' | 'reconcile'
12
+
13
+ /** Reference to a GitOps resource */
14
+ export interface GitOpsResourceRef {
15
+ kind: string
16
+ name: string
17
+ namespace: string
18
+ }
19
+
20
+ /**
21
+ * Standardized response format for all GitOps operations
22
+ * Returned by: sync, refresh, terminate, suspend, resume, reconcile endpoints
23
+ */
24
+ export interface GitOpsOperationResponse {
25
+ message: string
26
+ operation: GitOpsOperation
27
+ tool: GitOpsTool
28
+ resource: GitOpsResourceRef
29
+ requestedAt?: string
30
+ source?: GitOpsResourceRef // For sync-with-source operations
31
+ }
32
+
33
+ // ============================================================================
34
+ // STATUS TYPES
35
+ // ============================================================================
36
+
37
+ /**
38
+ * Unified sync status across GitOps tools
39
+ * - Synced: Resources match the desired state in git
40
+ * - OutOfSync: Resources differ from desired state
41
+ * - Reconciling: Sync operation in progress
42
+ * - Unknown: Status cannot be determined
43
+ */
44
+ export type SyncStatus = 'Synced' | 'OutOfSync' | 'Reconciling' | 'Unknown'
45
+
46
+ /**
47
+ * Unified health status across GitOps tools
48
+ * - Healthy: All resources are healthy
49
+ * - Progressing: Resources are being created/updated
50
+ * - Degraded: Some resources are unhealthy
51
+ * - Suspended: Reconciliation is paused
52
+ * - Missing: Expected resources don't exist
53
+ * - Unknown: Health cannot be determined
54
+ */
55
+ export type GitOpsHealthStatus = 'Healthy' | 'Progressing' | 'Degraded' | 'Suspended' | 'Missing' | 'Unknown'
56
+
57
+ /**
58
+ * Unified GitOps status combining sync and health information
59
+ */
60
+ export interface GitOpsStatus {
61
+ sync: SyncStatus
62
+ health: GitOpsHealthStatus
63
+ message?: string
64
+ lastSyncTime?: string
65
+ suspended: boolean
66
+ }
67
+
68
+ /**
69
+ * A resource managed by a GitOps application/kustomization
70
+ */
71
+ export interface ManagedResource {
72
+ group: string
73
+ kind: string
74
+ namespace: string
75
+ name: string
76
+ health?: GitOpsHealthStatus
77
+ sync?: SyncStatus
78
+ }
79
+
80
+ // ============================================================================
81
+ // FLUX CONDITION TYPES
82
+ // ============================================================================
83
+
84
+ /** Known FluxCD condition types */
85
+ export type FluxConditionType = 'Ready' | 'Reconciling' | 'Stalled' | 'Healthy'
86
+
87
+ export interface FluxCondition {
88
+ type: FluxConditionType | string // Known types + allow unknown for forward compatibility
89
+ status: 'True' | 'False' | 'Unknown'
90
+ reason?: string
91
+ message?: string
92
+ lastTransitionTime?: string
93
+ }
94
+
95
+ // ============================================================================
96
+ // FLUX STATUS MAPPER
97
+ // ============================================================================
98
+
99
+ /**
100
+ * Maps FluxCD conditions to unified GitOpsStatus
101
+ *
102
+ * FluxCD status patterns:
103
+ * - Ready=True: Synced and healthy
104
+ * - Ready=False + Stalled=True: Degraded
105
+ * - Ready=False + Reconciling=True: Reconciling
106
+ * - Ready=False (other): OutOfSync
107
+ * - Healthy=False: Deployed but unhealthy resources
108
+ * - spec.suspend=true: Suspended
109
+ */
110
+ export function fluxConditionsToGitOpsStatus(
111
+ conditions: FluxCondition[],
112
+ suspended: boolean
113
+ ): GitOpsStatus {
114
+ const readyCondition = conditions.find(c => c.type === 'Ready')
115
+ const reconcilingCondition = conditions.find(c => c.type === 'Reconciling')
116
+ const stalledCondition = conditions.find(c => c.type === 'Stalled')
117
+ const healthyCondition = conditions.find(c => c.type === 'Healthy')
118
+
119
+ // Handle suspended state
120
+ if (suspended) {
121
+ return {
122
+ sync: readyCondition?.status === 'True' ? 'Synced' : 'Unknown',
123
+ health: 'Suspended',
124
+ message: 'Reconciliation is suspended',
125
+ lastSyncTime: readyCondition?.lastTransitionTime,
126
+ suspended: true,
127
+ }
128
+ }
129
+
130
+ // Handle reconciling state
131
+ if (reconcilingCondition?.status === 'True') {
132
+ return {
133
+ sync: 'Reconciling',
134
+ health: 'Progressing',
135
+ message: reconcilingCondition.message || 'Reconciliation in progress',
136
+ lastSyncTime: readyCondition?.lastTransitionTime,
137
+ suspended: false,
138
+ }
139
+ }
140
+
141
+ // Handle ready state
142
+ if (readyCondition?.status === 'True') {
143
+ // Check if deployed resources are healthy
144
+ const isHealthy = healthyCondition?.status !== 'False'
145
+ return {
146
+ sync: 'Synced',
147
+ health: isHealthy ? 'Healthy' : 'Degraded',
148
+ message: isHealthy ? undefined : healthyCondition?.message,
149
+ lastSyncTime: readyCondition.lastTransitionTime,
150
+ suspended: false,
151
+ }
152
+ }
153
+
154
+ // Handle stalled state (permanent failure)
155
+ if (stalledCondition?.status === 'True') {
156
+ return {
157
+ sync: 'OutOfSync',
158
+ health: 'Degraded',
159
+ message: stalledCondition.message || 'Reconciliation stalled',
160
+ lastSyncTime: readyCondition?.lastTransitionTime,
161
+ suspended: false,
162
+ }
163
+ }
164
+
165
+ // Handle not ready state
166
+ if (readyCondition?.status === 'False') {
167
+ const reason = readyCondition.reason || ''
168
+ const isTransient = reason.includes('Progress') || reason.includes('Retry') || reason.includes('Wait')
169
+
170
+ return {
171
+ sync: 'OutOfSync',
172
+ health: isTransient ? 'Progressing' : 'Degraded',
173
+ message: readyCondition.message,
174
+ lastSyncTime: readyCondition.lastTransitionTime,
175
+ suspended: false,
176
+ }
177
+ }
178
+
179
+ // Unknown state
180
+ return {
181
+ sync: 'Unknown',
182
+ health: 'Unknown',
183
+ message: 'Status cannot be determined',
184
+ suspended: false,
185
+ }
186
+ }
187
+
188
+ // ============================================================================
189
+ // ARGO STATUS MAPPER
190
+ // ============================================================================
191
+
192
+ /** ArgoCD sync status values */
193
+ export type ArgoSyncStatus = 'Synced' | 'OutOfSync' | 'Unknown'
194
+
195
+ /** ArgoCD health status values */
196
+ export type ArgoHealthStatus = 'Healthy' | 'Progressing' | 'Degraded' | 'Suspended' | 'Missing' | 'Unknown'
197
+
198
+ /** ArgoCD operation phase values */
199
+ export type ArgoOperationPhase = 'Running' | 'Succeeded' | 'Failed' | 'Error' | 'Terminating'
200
+
201
+ /** ArgoCD sync result resource */
202
+ export interface ArgoSyncResultResource {
203
+ group?: string
204
+ version?: string
205
+ kind: string
206
+ namespace?: string
207
+ name: string
208
+ status?: string
209
+ message?: string
210
+ hookPhase?: string
211
+ }
212
+
213
+ /** ArgoCD sync result source */
214
+ export interface ArgoSyncResultSource {
215
+ repoURL?: string
216
+ path?: string
217
+ targetRevision?: string
218
+ chart?: string
219
+ }
220
+
221
+ export interface ArgoAppStatus {
222
+ sync?: {
223
+ status?: ArgoSyncStatus | string // Known types + allow unknown
224
+ revision?: string
225
+ }
226
+ health?: {
227
+ status?: ArgoHealthStatus | string // Known types + allow unknown
228
+ message?: string
229
+ }
230
+ operationState?: {
231
+ phase?: ArgoOperationPhase | string // Known types + allow unknown
232
+ message?: string
233
+ finishedAt?: string
234
+ syncResult?: {
235
+ revision?: string
236
+ source?: ArgoSyncResultSource
237
+ resources?: ArgoSyncResultResource[]
238
+ }
239
+ }
240
+ reconciledAt?: string
241
+ }
242
+
243
+ /**
244
+ * Maps ArgoCD Application status to unified GitOpsStatus
245
+ *
246
+ * ArgoCD status patterns:
247
+ * - sync.status: Synced, OutOfSync, Unknown
248
+ * - health.status: Healthy, Progressing, Degraded, Suspended, Missing, Unknown
249
+ * - operationState.phase: Running, Succeeded, Failed, Error
250
+ */
251
+ export function argoStatusToGitOpsStatus(status: ArgoAppStatus): GitOpsStatus {
252
+ const syncStatus = status.sync?.status
253
+ const healthStatus = status.health?.status
254
+ const opPhase = status.operationState?.phase
255
+
256
+ // Map sync status
257
+ let sync: SyncStatus = 'Unknown'
258
+ if (syncStatus === 'Synced') {
259
+ sync = 'Synced'
260
+ } else if (syncStatus === 'OutOfSync') {
261
+ sync = 'OutOfSync'
262
+ } else if (opPhase === 'Running') {
263
+ sync = 'Reconciling'
264
+ }
265
+
266
+ // Map health status
267
+ let health: GitOpsHealthStatus = 'Unknown'
268
+ const suspended = healthStatus === 'Suspended'
269
+
270
+ if (suspended) {
271
+ health = 'Suspended'
272
+ } else if (healthStatus === 'Healthy') {
273
+ health = 'Healthy'
274
+ } else if (healthStatus === 'Progressing') {
275
+ health = 'Progressing'
276
+ } else if (healthStatus === 'Degraded') {
277
+ health = 'Degraded'
278
+ } else if (healthStatus === 'Missing') {
279
+ health = 'Missing'
280
+ }
281
+
282
+ // Override with operation state if active
283
+ if (opPhase === 'Running') {
284
+ health = 'Progressing'
285
+ } else if (opPhase === 'Failed' || opPhase === 'Error') {
286
+ health = 'Degraded'
287
+ }
288
+
289
+ return {
290
+ sync,
291
+ health,
292
+ message: status.health?.message || status.operationState?.message,
293
+ lastSyncTime: status.reconciledAt || status.operationState?.finishedAt,
294
+ suspended,
295
+ }
296
+ }
297
+
298
+ // ============================================================================
299
+ // INVENTORY PARSERS
300
+ // ============================================================================
301
+
302
+ /**
303
+ * Parses Flux inventory entries into ManagedResource array
304
+ *
305
+ * Flux inventory format: "namespace_name_group_kind"
306
+ * Example: "default_my-config_core_ConfigMap" or "kube-system_coredns_apps_Deployment"
307
+ * Note: group can be empty for core resources (e.g., "default_my-config__ConfigMap")
308
+ *
309
+ * Malformed entries are filtered out rather than returning invalid resources.
310
+ */
311
+ export function parseFluxInventory(entries: Array<{ id: string; v?: string }>): ManagedResource[] {
312
+ return entries
313
+ .map(entry => {
314
+ // Format: namespace_name_group_kind
315
+ // The challenge is that namespace, name, and group can all contain underscores
316
+ // But the format is always: ns_name_group_kind where kind is the last part
317
+ const id = entry.id || ''
318
+ const parts = id.split('_')
319
+
320
+ // Minimum: namespace_name_group_kind (4 parts, though name could be empty which is invalid)
321
+ if (parts.length < 4) {
322
+ return null // Filter out malformed entries
323
+ }
324
+
325
+ // Kind is always last, group is second-to-last
326
+ const kind = parts[parts.length - 1]
327
+ const group = parts[parts.length - 2]
328
+ // Namespace is first, name is everything in between
329
+ const namespace = parts[0]
330
+ const name = parts.slice(1, -2).join('_')
331
+
332
+ // Validate required fields
333
+ if (!kind || !name) {
334
+ return null // Filter out entries with missing required fields
335
+ }
336
+
337
+ return {
338
+ group: group || '',
339
+ kind,
340
+ namespace,
341
+ name,
342
+ }
343
+ })
344
+ .filter((r): r is ManagedResource => r !== null)
345
+ }
346
+
347
+ /**
348
+ * Parses ArgoCD resource tree into ManagedResource array
349
+ *
350
+ * ArgoCD format: Array of {group, kind, namespace, name, health, status}
351
+ */
352
+ export interface ArgoResource {
353
+ group?: string
354
+ kind: string
355
+ namespace?: string
356
+ name: string
357
+ health?: {
358
+ status?: ArgoHealthStatus | string // Known types + allow unknown
359
+ message?: string
360
+ }
361
+ status?: ArgoSyncStatus | string // Known types + allow unknown
362
+ }
363
+
364
+ export function parseArgoResources(resources: ArgoResource[]): ManagedResource[] {
365
+ return resources.map(r => ({
366
+ group: r.group || '',
367
+ kind: r.kind,
368
+ namespace: r.namespace || '',
369
+ name: r.name,
370
+ health: mapArgoHealth(r.health?.status),
371
+ sync: r.status === 'Synced' ? 'Synced' : r.status === 'OutOfSync' ? 'OutOfSync' : undefined,
372
+ }))
373
+ }
374
+
375
+ /** Maps an ArgoCD health status string to the unified GitOpsHealthStatus type */
376
+ export function mapArgoHealth(status?: string): GitOpsHealthStatus | undefined {
377
+ if (!status) return undefined
378
+ switch (status) {
379
+ case 'Healthy': return 'Healthy'
380
+ case 'Progressing': return 'Progressing'
381
+ case 'Degraded': return 'Degraded'
382
+ case 'Suspended': return 'Suspended'
383
+ case 'Missing': return 'Missing'
384
+ default: return 'Unknown'
385
+ }
386
+ }
387
+
388
+ // ============================================================================
389
+ // UTILITY FUNCTIONS
390
+ // ============================================================================
391
+
392
+ /**
393
+ * Checks if a GitOps resource has issues (not synced or unhealthy)
394
+ */
395
+ export function hasGitOpsIssues(status: GitOpsStatus): boolean {
396
+ return status.sync !== 'Synced' ||
397
+ (status.health !== 'Healthy' && status.health !== 'Suspended')
398
+ }
399
+
400
+ /**
401
+ * Gets a single-line summary of GitOps status
402
+ */
403
+ export function getGitOpsStatusSummary(status: GitOpsStatus): string {
404
+ if (status.suspended) {
405
+ return 'Suspended'
406
+ }
407
+ if (status.sync === 'Reconciling') {
408
+ return 'Syncing...'
409
+ }
410
+ if (status.sync === 'Synced' && status.health === 'Healthy') {
411
+ return 'Synced'
412
+ }
413
+ if (status.health === 'Progressing') {
414
+ return 'Progressing'
415
+ }
416
+ if (status.sync === 'OutOfSync') {
417
+ return 'Out of Sync'
418
+ }
419
+ if (status.health === 'Degraded') {
420
+ return 'Degraded'
421
+ }
422
+ return status.sync
423
+ }
424
+
425
+ /**
426
+ * Gets the appropriate color class for a GitOps status
427
+ */
428
+ export function getGitOpsStatusColor(status: GitOpsStatus): string {
429
+ if (status.suspended) {
430
+ return 'status-degraded'
431
+ }
432
+ if (status.sync === 'Synced' && status.health === 'Healthy') {
433
+ return 'status-healthy'
434
+ }
435
+ if (status.health === 'Degraded') {
436
+ return 'status-unhealthy'
437
+ }
438
+ if (status.sync === 'OutOfSync' || status.health === 'Progressing') {
439
+ return 'status-degraded'
440
+ }
441
+ return 'status-unknown'
442
+ }
443
+
444
+ /**
445
+ * Groups managed resources by kind for display
446
+ */
447
+ export function groupManagedResourcesByKind(resources: ManagedResource[]): Map<string, ManagedResource[]> {
448
+ const grouped = new Map<string, ManagedResource[]>()
449
+
450
+ for (const resource of resources) {
451
+ const key = resource.kind
452
+ const existing = grouped.get(key) || []
453
+ existing.push(resource)
454
+ grouped.set(key, existing)
455
+ }
456
+
457
+ return grouped
458
+ }
@@ -0,0 +1,2 @@
1
+ export * from './core'
2
+ export * from './gitops'
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Central animation presets for consistent motion across the UI.
3
+ *
4
+ * All animations use GPU-composited properties only (translate, scale, opacity)
5
+ * to guarantee 60fps. The iOS-like spring easing gives a fast-in, gentle-out feel.
6
+ */
7
+
8
+ // -- Easing ------------------------------------------------------------------
9
+
10
+ // Tailwind-safe easing (no spaces — Tailwind splits class strings on whitespace)
11
+ const TW_EASE = 'ease-[cubic-bezier(0.32,0.72,0,1)]'
12
+
13
+ /** Raw CSS easing for inline transition strings */
14
+ export const CSS_EASE = 'cubic-bezier(0.32, 0.72, 0, 1)'
15
+
16
+ // -- Durations (ms) ----------------------------------------------------------
17
+
18
+ /** Standard UI transitions: overlays, panels, toasts */
19
+ export const DURATION_NORMAL = 300
20
+ /** Dock height, subtle layout shifts */
21
+ export const DURATION_DOCK = 250
22
+ /** Toast exit animation */
23
+ export const DURATION_TOAST_EXIT = 200
24
+
25
+ // -- Tailwind class presets ---------------------------------------------------
26
+ // Reusable class fragments — import and spread into clsx() calls.
27
+
28
+ /** Drawer slide from right — use with translate-x-0/translate-x-full + opacity.
29
+ * Also handles expand/collapse (width) so a single transition covers both. */
30
+ export const TRANSITION_DRAWER =
31
+ `transition-[translate,opacity,width] duration-300 ${TW_EASE} will-change-[transform,width]`
32
+
33
+
34
+ /** Overlay backdrop fade */
35
+ export const TRANSITION_BACKDROP =
36
+ 'transition-opacity duration-200'
37
+
38
+ /** Overlay panel scale + fade — use with scale-100/scale-[0.97] + opacity */
39
+ export const TRANSITION_PANEL =
40
+ `transition-[translate,scale,opacity] duration-300 ${TW_EASE}`
@@ -0,0 +1,185 @@
1
+ import type { APIResource } from '../types'
2
+
3
+ // Group resources by category for sidebar display
4
+ export interface ResourceCategory {
5
+ name: string
6
+ resources: APIResource[]
7
+ }
8
+
9
+ // Known core resource categories
10
+ const WORKLOAD_KINDS = ['Pod', 'Deployment', 'Rollout', 'DaemonSet', 'StatefulSet', 'ReplicaSet', 'Job', 'CronJob']
11
+ const NETWORKING_KINDS = ['Service', 'Ingress', 'IngressClass', 'NetworkPolicy', 'Endpoints', 'EndpointSlice']
12
+ const CONFIG_KINDS = ['ConfigMap', 'Secret', 'HorizontalPodAutoscaler', 'PodDisruptionBudget', 'LimitRange', 'ResourceQuota', 'PriorityClass', 'RuntimeClass', 'Lease', 'MutatingWebhookConfiguration', 'ValidatingWebhookConfiguration']
13
+ const STORAGE_KINDS = ['PersistentVolumeClaim', 'PersistentVolume', 'StorageClass', 'VolumeAttachment']
14
+ const ACCESS_CONTROL_KINDS = ['ServiceAccount', 'Role', 'ClusterRole', 'RoleBinding', 'ClusterRoleBinding']
15
+ const CLUSTER_KINDS = ['Node', 'Namespace', 'Event']
16
+
17
+ // Core resources that must always be present (fallback if API discovery misses them)
18
+ export const CORE_RESOURCES: APIResource[] = [
19
+ { group: '', version: 'v1', kind: 'Pod', name: 'pods', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
20
+ { group: '', version: 'v1', kind: 'Service', name: 'services', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
21
+ { group: '', version: 'v1', kind: 'ConfigMap', name: 'configmaps', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
22
+ { group: '', version: 'v1', kind: 'Secret', name: 'secrets', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
23
+ { group: '', version: 'v1', kind: 'Node', name: 'nodes', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
24
+ { group: '', version: 'v1', kind: 'Namespace', name: 'namespaces', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
25
+ { group: '', version: 'v1', kind: 'ServiceAccount', name: 'serviceaccounts', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
26
+ { group: '', version: 'v1', kind: 'PersistentVolumeClaim', name: 'persistentvolumeclaims', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
27
+ { group: '', version: 'v1', kind: 'PersistentVolume', name: 'persistentvolumes', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
28
+ { group: 'apps', version: 'v1', kind: 'Deployment', name: 'deployments', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
29
+ { group: 'apps', version: 'v1', kind: 'DaemonSet', name: 'daemonsets', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
30
+ { group: 'apps', version: 'v1', kind: 'StatefulSet', name: 'statefulsets', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
31
+ { group: 'apps', version: 'v1', kind: 'ReplicaSet', name: 'replicasets', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
32
+ { group: 'batch', version: 'v1', kind: 'Job', name: 'jobs', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
33
+ { group: 'batch', version: 'v1', kind: 'CronJob', name: 'cronjobs', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
34
+ { group: 'networking.k8s.io', version: 'v1', kind: 'Ingress', name: 'ingresses', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
35
+ { group: 'networking.k8s.io', version: 'v1', kind: 'NetworkPolicy', name: 'networkpolicies', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
36
+ { group: 'autoscaling', version: 'v2', kind: 'HorizontalPodAutoscaler', name: 'horizontalpodautoscalers', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
37
+ { group: '', version: 'v1', kind: 'Event', name: 'events', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
38
+ { group: 'rbac.authorization.k8s.io', version: 'v1', kind: 'Role', name: 'roles', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
39
+ { group: 'rbac.authorization.k8s.io', version: 'v1', kind: 'ClusterRole', name: 'clusterroles', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
40
+ { group: 'rbac.authorization.k8s.io', version: 'v1', kind: 'RoleBinding', name: 'rolebindings', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
41
+ { group: 'rbac.authorization.k8s.io', version: 'v1', kind: 'ClusterRoleBinding', name: 'clusterrolebindings', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
42
+ { group: 'networking.k8s.io', version: 'v1', kind: 'IngressClass', name: 'ingressclasses', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
43
+ { group: 'admissionregistration.k8s.io', version: 'v1', kind: 'MutatingWebhookConfiguration', name: 'mutatingwebhookconfigurations', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
44
+ { group: 'admissionregistration.k8s.io', version: 'v1', kind: 'ValidatingWebhookConfiguration', name: 'validatingwebhookconfigurations', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
45
+ { group: 'scheduling.k8s.io', version: 'v1', kind: 'PriorityClass', name: 'priorityclasses', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
46
+ { group: 'node.k8s.io', version: 'v1', kind: 'RuntimeClass', name: 'runtimeclasses', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
47
+ { group: 'coordination.k8s.io', version: 'v1', kind: 'Lease', name: 'leases', namespaced: true, isCrd: false, verbs: ['list', 'get', 'watch'] },
48
+ { group: 'storage.k8s.io', version: 'v1', kind: 'VolumeAttachment', name: 'volumeattachments', namespaced: false, isCrd: false, verbs: ['list', 'get', 'watch'] },
49
+ ]
50
+
51
+ // Resources that should be hidden from the sidebar
52
+ const HIDDEN_KINDS = ['PodMetrics', 'NodeMetrics']
53
+
54
+ export function categorizeResources(resources: APIResource[]): ResourceCategory[] {
55
+ const listableResources = resources.filter(r =>
56
+ r.verbs?.includes('list') && !HIDDEN_KINDS.includes(r.kind) &&
57
+ !(r.kind === 'Event' && r.group === 'events.k8s.io')
58
+ )
59
+
60
+ const seenKinds = new Map<string, APIResource>()
61
+ const dedupKey = (r: APIResource) => r.isCrd ? `${r.group}/${r.kind}` : r.kind
62
+
63
+ for (const resource of CORE_RESOURCES) {
64
+ seenKinds.set(dedupKey(resource), resource)
65
+ }
66
+ for (const resource of listableResources) {
67
+ seenKinds.set(dedupKey(resource), resource)
68
+ }
69
+ const uniqueResources = Array.from(seenKinds.values())
70
+
71
+ const categoryMap = new Map<string, APIResource[]>()
72
+ function addToCategory(name: string, items: APIResource[]) {
73
+ if (items.length === 0) return
74
+ const existing = categoryMap.get(name) || []
75
+ categoryMap.set(name, [...existing, ...items])
76
+ }
77
+
78
+ const coreResources = uniqueResources.filter(r => !r.isCrd)
79
+ const workloads = coreResources.filter(r => WORKLOAD_KINDS.includes(r.kind))
80
+ const networking = coreResources.filter(r => NETWORKING_KINDS.includes(r.kind))
81
+ const config = coreResources.filter(r => CONFIG_KINDS.includes(r.kind))
82
+ const storage = coreResources.filter(r => STORAGE_KINDS.includes(r.kind))
83
+ const accessControl = coreResources.filter(r => ACCESS_CONTROL_KINDS.includes(r.kind))
84
+ const cluster = coreResources.filter(r => CLUSTER_KINDS.includes(r.kind))
85
+
86
+ const crds = uniqueResources.filter(r => r.isCrd)
87
+ const crdGroups = new Map<string, APIResource[]>()
88
+ for (const crd of crds) {
89
+ const group = crd.group || 'custom'
90
+ if (!crdGroups.has(group)) crdGroups.set(group, [])
91
+ crdGroups.get(group)!.push(crd)
92
+ }
93
+
94
+ addToCategory('Workloads', workloads)
95
+ addToCategory('Networking', networking)
96
+ addToCategory('Configuration', config)
97
+ addToCategory('Storage', storage)
98
+ addToCategory('Access Control', accessControl)
99
+ addToCategory('Cluster', cluster)
100
+
101
+ for (const [group, groupResources] of crdGroups) {
102
+ addToCategory(formatGroupName(group), groupResources)
103
+ }
104
+
105
+ return Array.from(categoryMap.entries()).map(([name, items]) => ({
106
+ name,
107
+ resources: sortResources(items),
108
+ }))
109
+ }
110
+
111
+ export function formatGroupName(group: string): string {
112
+ const knownGroups: Record<string, string> = {
113
+ 'argoproj.io': 'Argo',
114
+ 'cert-manager.io': 'Cert Manager',
115
+ 'acme.cert-manager.io': 'Cert Manager',
116
+ 'istio.io': 'Istio',
117
+ 'networking.istio.io': 'Istio',
118
+ 'security.istio.io': 'Istio',
119
+ 'telemetry.istio.io': 'Istio',
120
+ 'monitoring.coreos.com': 'Prometheus',
121
+ 'velero.io': 'Velero',
122
+ 'external-secrets.io': 'External Secrets',
123
+ 'keda.sh': 'KEDA',
124
+ 'gateway.networking.k8s.io': 'Gateway API',
125
+ 'traefik.io': 'Traefik',
126
+ 'traefik.containo.us': 'Traefik',
127
+ 'pkg.crossplane.io': 'Crossplane',
128
+ 'apiextensions.crossplane.io': 'Crossplane',
129
+ 'source.toolkit.fluxcd.io': 'Flux',
130
+ 'helm.toolkit.fluxcd.io': 'Flux',
131
+ 'kustomize.toolkit.fluxcd.io': 'Flux',
132
+ 'notification.toolkit.fluxcd.io': 'Flux',
133
+ 'image.toolkit.fluxcd.io': 'Flux',
134
+ 'serving.knative.dev': 'Knative',
135
+ 'eventing.knative.dev': 'Knative',
136
+ 'messaging.knative.dev': 'Knative',
137
+ 'sources.knative.dev': 'Knative',
138
+ 'networking.internal.knative.dev': 'Knative',
139
+ 'flows.knative.dev': 'Knative',
140
+ 'kafka.strimzi.io': 'Strimzi',
141
+ 'tekton.dev': 'Tekton',
142
+ 'linkerd.io': 'Linkerd',
143
+ 'policy.linkerd.io': 'Linkerd',
144
+ 'cilium.io': 'Cilium',
145
+ 'aquasecurity.github.io': 'Trivy',
146
+ 'bitnami.com': 'Bitnami',
147
+ 'elasticsearch.k8s.elastic.co': 'Elastic',
148
+ 'kibana.k8s.elastic.co': 'Elastic',
149
+ 'apm.k8s.elastic.co': 'Elastic',
150
+ 'beat.k8s.elastic.co': 'Elastic',
151
+ 'agent.k8s.elastic.co': 'Elastic',
152
+ 'maps.k8s.elastic.co': 'Elastic',
153
+ 'logstash.k8s.elastic.co': 'Elastic',
154
+ 'jaegertracing.io': 'Jaeger',
155
+ 'opentelemetry.io': 'OpenTelemetry',
156
+ 'projectcalico.org': 'Calico',
157
+ 'crd.projectcalico.org': 'Calico',
158
+ 'projectcontour.io': 'Contour',
159
+ 'ceph.rook.io': 'Rook',
160
+ 'kyverno.io': 'Kyverno',
161
+ 'k8s.nginx.org': 'NGINX',
162
+ 'sparkoperator.k8s.io': 'Spark',
163
+ 'kubeflow.org': 'Kubeflow',
164
+ 'snapshot.storage.k8s.io': 'Snapshots',
165
+ }
166
+ return knownGroups[group] || group
167
+ }
168
+
169
+ export function shortenGroupName(group: string): string {
170
+ return group
171
+ .replace(/\.(io|com|org|dev|sh)$/, '')
172
+ .replace(/\.k8s$/, '')
173
+ }
174
+
175
+ function sortResources(resources: APIResource[]): APIResource[] {
176
+ return [...resources].sort((a, b) => a.kind.localeCompare(b.kind))
177
+ }
178
+
179
+ export function getKindLabel(kind: string): string {
180
+ return kind.replace(/([A-Z])/g, ' $1').trim()
181
+ }
182
+
183
+ export function getKindPlural(resource: APIResource): string {
184
+ return resource.name
185
+ }