@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,302 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Copy,
|
|
4
|
+
Check,
|
|
5
|
+
RefreshCw,
|
|
6
|
+
Pencil,
|
|
7
|
+
Save,
|
|
8
|
+
XCircle,
|
|
9
|
+
AlertTriangle,
|
|
10
|
+
} from 'lucide-react'
|
|
11
|
+
import { stringify as yamlStringify } from 'yaml'
|
|
12
|
+
import { CodeViewer } from '../ui/CodeViewer'
|
|
13
|
+
import { YamlEditor } from '../ui/YamlEditor'
|
|
14
|
+
import type { SelectedResource } from '../../types'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// SUCCESS ANIMATION
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export function SaveSuccessAnimation() {
|
|
21
|
+
return (
|
|
22
|
+
<div className="absolute top-0 left-0 right-0 z-50 pointer-events-none">
|
|
23
|
+
<div className="flex justify-center animate-fade-in-out">
|
|
24
|
+
<div className="mt-2 px-4 py-2 bg-green-600/90 dark:bg-green-500/90 backdrop-blur-sm rounded-lg shadow-lg flex items-center gap-2">
|
|
25
|
+
<Check className="w-4 h-4 text-white" />
|
|
26
|
+
<span className="text-white text-sm font-medium">Saved</span>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// EDITABLE YAML VIEW
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
// Get edit warning for resource types with limited editability
|
|
38
|
+
function getEditWarning(kind: string): { message: string; tip: string; learnMoreUrl?: string } | null {
|
|
39
|
+
const k = kind.toLowerCase()
|
|
40
|
+
if (k === 'pods' || k === 'pod') {
|
|
41
|
+
return {
|
|
42
|
+
message: 'Pods have limited editability.',
|
|
43
|
+
tip: 'Green highlighted lines can be changed. Edit the parent Deployment instead for other fields.',
|
|
44
|
+
learnMoreUrl: 'https://kubernetes.io/docs/concepts/workloads/pods/#pod-update-and-replacement'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (k === 'jobs' || k === 'job') {
|
|
48
|
+
return {
|
|
49
|
+
message: 'Jobs cannot be modified after creation.',
|
|
50
|
+
tip: 'Delete and recreate the Job to make changes.',
|
|
51
|
+
learnMoreUrl: 'https://kubernetes.io/docs/concepts/workloads/controllers/job/'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Parse and simplify Kubernetes error messages
|
|
58
|
+
function formatSaveError(error: string): { summary: string; details?: string } {
|
|
59
|
+
if (error.includes('is invalid:')) {
|
|
60
|
+
const parts = error.split('is invalid:')
|
|
61
|
+
const errorPart = parts[1]?.trim() || ''
|
|
62
|
+
|
|
63
|
+
if (errorPart.includes('Forbidden:')) {
|
|
64
|
+
const forbiddenMatch = errorPart.match(/([^:]+):\s*Forbidden:\s*([^.{]+)/)
|
|
65
|
+
if (forbiddenMatch) {
|
|
66
|
+
return {
|
|
67
|
+
summary: `Cannot update ${forbiddenMatch[1]}: ${forbiddenMatch[2].trim()}`,
|
|
68
|
+
details: error.length > 200 ? error : undefined
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const summaryMatch = errorPart.match(/^([^{]+)/)
|
|
74
|
+
if (summaryMatch) {
|
|
75
|
+
return {
|
|
76
|
+
summary: summaryMatch[1].trim(),
|
|
77
|
+
details: error.length > 200 ? error : undefined
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (error.length > 150) {
|
|
83
|
+
return {
|
|
84
|
+
summary: error.substring(0, 150) + '...',
|
|
85
|
+
details: error
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { summary: error }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface EditableYamlViewProps {
|
|
93
|
+
resource: SelectedResource
|
|
94
|
+
data: any
|
|
95
|
+
onCopy: (text: string) => void
|
|
96
|
+
copied: boolean
|
|
97
|
+
/** Called after a successful save so the parent can refetch */
|
|
98
|
+
onSaved?: () => void
|
|
99
|
+
/** Save handler — injected by the platform wrapper */
|
|
100
|
+
onSave?: (params: { kind: string; namespace: string; name: string; yaml: string }) => Promise<void>
|
|
101
|
+
/** Whether a save is in progress */
|
|
102
|
+
isSaving?: boolean
|
|
103
|
+
/** Error message from the last save attempt */
|
|
104
|
+
saveError?: string | null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function EditableYamlView({ resource, data, onCopy, copied, onSaved, onSave, isSaving, saveError }: EditableYamlViewProps) {
|
|
108
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
109
|
+
const [editedYaml, setEditedYaml] = useState('')
|
|
110
|
+
const [yamlErrors, setYamlErrors] = useState<string[]>([])
|
|
111
|
+
const [showErrorDetails, setShowErrorDetails] = useState(false)
|
|
112
|
+
|
|
113
|
+
// Convert resource to YAML for editing
|
|
114
|
+
const convertToYaml = useCallback((d: any) => {
|
|
115
|
+
if (!d) return ''
|
|
116
|
+
const cleaned = { ...d }
|
|
117
|
+
delete cleaned.status
|
|
118
|
+
if (cleaned.metadata) {
|
|
119
|
+
delete cleaned.metadata.managedFields
|
|
120
|
+
delete cleaned.metadata.resourceVersion
|
|
121
|
+
delete cleaned.metadata.uid
|
|
122
|
+
delete cleaned.metadata.creationTimestamp
|
|
123
|
+
delete cleaned.metadata.generation
|
|
124
|
+
}
|
|
125
|
+
return yamlStringify(cleaned, { lineWidth: 0, indent: 2 })
|
|
126
|
+
}, [])
|
|
127
|
+
|
|
128
|
+
const handleStartEdit = useCallback(() => {
|
|
129
|
+
setEditedYaml(convertToYaml(data))
|
|
130
|
+
setYamlErrors([])
|
|
131
|
+
setIsEditing(true)
|
|
132
|
+
}, [data, convertToYaml])
|
|
133
|
+
|
|
134
|
+
const handleCancelEdit = useCallback(() => {
|
|
135
|
+
setIsEditing(false)
|
|
136
|
+
setEditedYaml('')
|
|
137
|
+
setYamlErrors([])
|
|
138
|
+
}, [])
|
|
139
|
+
|
|
140
|
+
const handleSaveEdit = useCallback(async () => {
|
|
141
|
+
if (yamlErrors.length > 0 || !onSave) return
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await onSave({
|
|
145
|
+
kind: resource.kind,
|
|
146
|
+
namespace: resource.namespace,
|
|
147
|
+
name: resource.name,
|
|
148
|
+
yaml: editedYaml,
|
|
149
|
+
})
|
|
150
|
+
setIsEditing(false)
|
|
151
|
+
setEditedYaml('')
|
|
152
|
+
setTimeout(() => onSaved?.(), 1000)
|
|
153
|
+
} catch {
|
|
154
|
+
// Error handled by caller via saveError prop
|
|
155
|
+
}
|
|
156
|
+
}, [onSave, resource, editedYaml, yamlErrors, onSaved])
|
|
157
|
+
|
|
158
|
+
const handleYamlValidate = useCallback((_isValid: boolean, errors: string[]) => {
|
|
159
|
+
setYamlErrors(errors)
|
|
160
|
+
}, [])
|
|
161
|
+
|
|
162
|
+
const yamlContent = yamlStringify(data, { lineWidth: 0, indent: 2 })
|
|
163
|
+
const editWarning = getEditWarning(resource.kind)
|
|
164
|
+
const formattedError = saveError ? formatSaveError(saveError) : null
|
|
165
|
+
const isPending = isSaving ?? false
|
|
166
|
+
|
|
167
|
+
if (isEditing) {
|
|
168
|
+
return (
|
|
169
|
+
<div className="flex flex-col h-full">
|
|
170
|
+
<div className="flex items-center justify-between px-4 py-2 border-b border-theme-border bg-theme-elevated/50">
|
|
171
|
+
<div className="flex items-center gap-2">
|
|
172
|
+
<Pencil className="w-4 h-4 text-blue-400" />
|
|
173
|
+
<span className="text-sm font-medium text-theme-text-primary">Editing Resource</span>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="flex items-center gap-2">
|
|
176
|
+
<button
|
|
177
|
+
onClick={handleCancelEdit}
|
|
178
|
+
disabled={isPending}
|
|
179
|
+
className="flex items-center gap-1 px-3 py-1.5 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-surface rounded border border-theme-border disabled:opacity-50"
|
|
180
|
+
>
|
|
181
|
+
<XCircle className="w-3.5 h-3.5" />
|
|
182
|
+
Cancel
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
onClick={handleSaveEdit}
|
|
186
|
+
disabled={isPending || yamlErrors.length > 0}
|
|
187
|
+
className="flex items-center gap-1 px-3 py-1.5 text-xs text-white bg-blue-600 hover:bg-blue-700 rounded disabled:opacity-50 disabled:cursor-not-allowed"
|
|
188
|
+
>
|
|
189
|
+
{isPending ? (
|
|
190
|
+
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
|
|
191
|
+
) : (
|
|
192
|
+
<Save className="w-3.5 h-3.5" />
|
|
193
|
+
)}
|
|
194
|
+
{isPending ? 'Saving...' : 'Save'}
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{editWarning && (
|
|
200
|
+
<div className="px-4 py-2.5 bg-amber-500/10 dark:bg-yellow-500/10 border-b border-amber-300 dark:border-yellow-500/30">
|
|
201
|
+
<div className="flex items-start gap-2">
|
|
202
|
+
<AlertTriangle className="w-4 h-4 text-amber-600 dark:text-yellow-300 mt-0.5 shrink-0" />
|
|
203
|
+
<div className="text-xs">
|
|
204
|
+
<span className="font-medium text-amber-700 dark:text-yellow-300">{editWarning.message}</span>
|
|
205
|
+
<span className="text-amber-600 dark:text-yellow-300/80 ml-1">{editWarning.tip}</span>
|
|
206
|
+
{editWarning.learnMoreUrl && (
|
|
207
|
+
<a
|
|
208
|
+
href={editWarning.learnMoreUrl}
|
|
209
|
+
target="_blank"
|
|
210
|
+
rel="noopener noreferrer"
|
|
211
|
+
className="ml-1.5 text-blue-600 dark:text-blue-300 hover:underline"
|
|
212
|
+
>
|
|
213
|
+
Learn more →
|
|
214
|
+
</a>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
|
|
221
|
+
{yamlErrors.length > 0 && (
|
|
222
|
+
<div className="px-4 py-2 bg-red-500/10 border-b border-red-300 dark:border-red-500/30">
|
|
223
|
+
<div className="flex items-start gap-2">
|
|
224
|
+
<AlertTriangle className="w-4 h-4 text-red-600 dark:text-red-300 mt-0.5 shrink-0" />
|
|
225
|
+
<div className="text-xs text-red-600 dark:text-red-300">
|
|
226
|
+
{yamlErrors.map((err, i) => (
|
|
227
|
+
<div key={i}>{err}</div>
|
|
228
|
+
))}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
{formattedError && (
|
|
235
|
+
<div className="px-4 py-2 bg-red-500/10 border-b border-red-300 dark:border-red-500/30">
|
|
236
|
+
<div className="flex items-start gap-2">
|
|
237
|
+
<AlertTriangle className="w-4 h-4 text-red-600 dark:text-red-300 mt-0.5 shrink-0" />
|
|
238
|
+
<div className="text-xs text-red-600 dark:text-red-300 flex-1">
|
|
239
|
+
<div className="font-medium">Save failed</div>
|
|
240
|
+
<div className="mt-1">{formattedError.summary}</div>
|
|
241
|
+
{formattedError.details && (
|
|
242
|
+
<button
|
|
243
|
+
onClick={() => setShowErrorDetails(!showErrorDetails)}
|
|
244
|
+
className="mt-1 text-red-500 dark:text-red-300 hover:text-red-700 dark:hover:text-red-200 underline"
|
|
245
|
+
>
|
|
246
|
+
{showErrorDetails ? 'Hide details' : 'Show details'}
|
|
247
|
+
</button>
|
|
248
|
+
)}
|
|
249
|
+
{showErrorDetails && formattedError.details && (
|
|
250
|
+
<pre className="mt-2 p-2 bg-red-500/10 rounded text-[10px] whitespace-pre-wrap break-all max-h-40 overflow-auto">
|
|
251
|
+
{formattedError.details}
|
|
252
|
+
</pre>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
|
|
259
|
+
<div className="flex-1 min-h-0">
|
|
260
|
+
<YamlEditor
|
|
261
|
+
value={editedYaml}
|
|
262
|
+
onChange={setEditedYaml}
|
|
263
|
+
onValidate={handleYamlValidate}
|
|
264
|
+
height="100%"
|
|
265
|
+
kind={resource.kind}
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Read-only mode
|
|
273
|
+
return (
|
|
274
|
+
<div className="p-4">
|
|
275
|
+
<div className="flex items-center justify-between mb-2">
|
|
276
|
+
<span className="text-sm font-medium text-theme-text-secondary">YAML</span>
|
|
277
|
+
<div className="flex items-center gap-2">
|
|
278
|
+
<button
|
|
279
|
+
onClick={handleStartEdit}
|
|
280
|
+
className="flex items-center gap-1 px-2 py-1 text-xs text-blue-400 hover:text-blue-300 hover:bg-theme-elevated rounded"
|
|
281
|
+
>
|
|
282
|
+
<Pencil className="w-3.5 h-3.5" />
|
|
283
|
+
Edit
|
|
284
|
+
</button>
|
|
285
|
+
<button
|
|
286
|
+
onClick={() => onCopy(yamlContent)}
|
|
287
|
+
className="flex items-center gap-1 px-2 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
|
|
288
|
+
>
|
|
289
|
+
{copied ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
|
|
290
|
+
Copy
|
|
291
|
+
</button>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
<CodeViewer
|
|
295
|
+
code={yamlContent}
|
|
296
|
+
language="yaml"
|
|
297
|
+
showLineNumbers
|
|
298
|
+
maxHeight="calc(100vh - 250px)"
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
)
|
|
302
|
+
}
|