@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.
- package/package.json +63 -0
- package/src/components/dock/BottomDock.tsx +204 -0
- package/src/components/dock/DockContext.tsx +212 -0
- package/src/components/dock/TerminalTab.tsx +333 -0
- package/src/components/dock/index.ts +3 -0
- package/src/components/gitops/GitOpsActions.tsx +227 -0
- package/src/components/gitops/GitOpsStatusBadge.tsx +221 -0
- package/src/components/gitops/ManagedResourcesList.tsx +210 -0
- package/src/components/gitops/SyncCountdown.tsx +146 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/logs/JsonLogLine.tsx +90 -0
- package/src/components/logs/LogCore.tsx +553 -0
- package/src/components/logs/LogToolbarSelects.tsx +74 -0
- package/src/components/logs/LogsViewer.tsx +146 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +263 -0
- package/src/components/logs/index.ts +13 -0
- package/src/components/logs/useLogBuffer.ts +138 -0
- package/src/components/logs/useLogSearch.ts +151 -0
- package/src/components/logs/useLogStream.ts +61 -0
- package/src/components/resources/ResourcesView.tsx +5494 -0
- package/src/components/resources/index.ts +17 -0
- package/src/components/resources/renderers/AlertRenderer.tsx +166 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +233 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +89 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +194 -0
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +79 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +68 -0
- package/src/components/resources/renderers/CertificateRenderer.tsx +209 -0
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +123 -0
- package/src/components/resources/renderers/ChallengeRenderer.tsx +111 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +164 -0
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +140 -0
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +153 -0
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +221 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +124 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +86 -0
- package/src/components/resources/renderers/EventRenderer.tsx +175 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +119 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +172 -0
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +242 -0
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +263 -0
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +68 -0
- package/src/components/resources/renderers/GatewayRenderer.tsx +218 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +476 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +154 -0
- package/src/components/resources/renderers/HPARenderer.tsx +164 -0
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +303 -0
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +167 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +49 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +104 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +175 -0
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +176 -0
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +125 -0
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +84 -0
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +112 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +288 -0
- package/src/components/resources/renderers/JobRenderer.tsx +103 -0
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +157 -0
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +188 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +148 -0
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +90 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +136 -0
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +151 -0
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +70 -0
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +309 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +142 -0
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +232 -0
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +97 -0
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +74 -0
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +138 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +220 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +151 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +352 -0
- package/src/components/resources/renderers/LeaseRenderer.tsx +93 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +208 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +282 -0
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +153 -0
- package/src/components/resources/renderers/OrderRenderer.tsx +108 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +98 -0
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +138 -0
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +126 -0
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +63 -0
- package/src/components/resources/renderers/PodRenderer.tsx +677 -0
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +50 -0
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +62 -0
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +71 -0
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +97 -0
- package/src/components/resources/renderers/RoleRenderer.tsx +192 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +227 -0
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +75 -0
- package/src/components/resources/renderers/SbomReportRenderer.tsx +98 -0
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +121 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +321 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +111 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +81 -0
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +65 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +98 -0
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +195 -0
- package/src/components/resources/renderers/StorageClassRenderer.tsx +82 -0
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +257 -0
- package/src/components/resources/renderers/VPARenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +73 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +161 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +134 -0
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +36 -0
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +347 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +156 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +202 -0
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +91 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +311 -0
- package/src/components/resources/renderers/argo-cells.tsx +118 -0
- package/src/components/resources/renderers/certmanager-cells.tsx +164 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +161 -0
- package/src/components/resources/renderers/eso-cells.tsx +115 -0
- package/src/components/resources/renderers/flux-cells.tsx +253 -0
- package/src/components/resources/renderers/index.ts +105 -0
- package/src/components/resources/renderers/istio-cells.tsx +210 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +107 -0
- package/src/components/resources/renderers/keda-cells.tsx +94 -0
- package/src/components/resources/renderers/knative-cells.tsx +396 -0
- package/src/components/resources/renderers/kyverno-cells.tsx +85 -0
- package/src/components/resources/renderers/prometheus-cells.tsx +74 -0
- package/src/components/resources/renderers/traefik-cells.tsx +120 -0
- package/src/components/resources/renderers/trivy-cells.tsx +153 -0
- package/src/components/resources/renderers/trivy-shared.tsx +67 -0
- package/src/components/resources/renderers/velero-cells.tsx +164 -0
- package/src/components/resources/resource-utils-argo.ts +155 -0
- package/src/components/resources/resource-utils-certmanager.ts +198 -0
- package/src/components/resources/resource-utils-cnpg.ts +344 -0
- package/src/components/resources/resource-utils-eso.ts +385 -0
- package/src/components/resources/resource-utils-flux.ts +186 -0
- package/src/components/resources/resource-utils-istio.ts +372 -0
- package/src/components/resources/resource-utils-karpenter.ts +150 -0
- package/src/components/resources/resource-utils-keda.ts +203 -0
- package/src/components/resources/resource-utils-knative.ts +218 -0
- package/src/components/resources/resource-utils-kyverno.ts +224 -0
- package/src/components/resources/resource-utils-prometheus.ts +159 -0
- package/src/components/resources/resource-utils-traefik.ts +180 -0
- package/src/components/resources/resource-utils-trivy.ts +146 -0
- package/src/components/resources/resource-utils-velero.ts +293 -0
- package/src/components/resources/resource-utils.ts +1558 -0
- package/src/components/shared/EditableYamlView.tsx +302 -0
- package/src/components/shared/ResourceActionsBar.tsx +924 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +557 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/shared.tsx +614 -0
- package/src/components/topology/GroupNode.tsx +198 -0
- package/src/components/topology/K8sResourceNode.tsx +440 -0
- package/src/components/topology/TopologyControls.tsx +62 -0
- package/src/components/topology/TopologyFilterSidebar.tsx +377 -0
- package/src/components/topology/TopologyGraph.tsx +748 -0
- package/src/components/topology/TopologySearch.tsx +304 -0
- package/src/components/topology/index.ts +7 -0
- package/src/components/topology/layout.ts +734 -0
- package/src/components/topology/layout.worker.ts +272 -0
- package/src/components/topology/topology.css +129 -0
- package/src/components/ui/CodeViewer.tsx +451 -0
- package/src/components/ui/ConfirmDialog.tsx +188 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +57 -0
- package/src/components/ui/HealthRing.tsx +72 -0
- package/src/components/ui/MetricsChart.tsx +259 -0
- package/src/components/ui/ResourceBar.tsx +68 -0
- package/src/components/ui/Toast.tsx +225 -0
- package/src/components/ui/Tooltip.tsx +165 -0
- package/src/components/ui/YamlEditor.tsx +280 -0
- package/src/components/ui/drawer-components.tsx +792 -0
- package/src/components/ui/index.ts +10 -0
- package/src/components/workload/ResourceDetailDrawer.tsx +158 -0
- package/src/components/workload/WorkloadView.tsx +1109 -0
- package/src/components/workload/index.ts +2 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useAnimatedUnmount.ts +30 -0
- package/src/hooks/useKeyboardShortcuts.tsx +336 -0
- package/src/hooks/useRefreshAnimation.ts +58 -0
- package/src/index.ts +35 -0
- package/src/types/core.ts +894 -0
- package/src/types/gitops.ts +458 -0
- package/src/types/index.ts +2 -0
- package/src/utils/animation.ts +40 -0
- package/src/utils/api-resources.ts +185 -0
- package/src/utils/badge-colors.ts +399 -0
- package/src/utils/context-name.ts +72 -0
- package/src/utils/download.ts +19 -0
- package/src/utils/format.ts +233 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/log-format.ts +300 -0
- package/src/utils/navigation.ts +119 -0
- package/src/utils/resource-hierarchy.ts +630 -0
- package/src/utils/resource-icons.ts +206 -0
- package/src/vite-env.d.ts +1 -0
- 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,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
|
+
}
|