@skyhook-io/radar-app 0.1.1
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/README.md +67 -0
- package/package.json +80 -0
- package/src/App.tsx +1538 -0
- package/src/RadarApp.tsx +145 -0
- package/src/api/apiResources.ts +28 -0
- package/src/api/client.ts +2583 -0
- package/src/api/config.ts +116 -0
- package/src/api/traffic.ts +139 -0
- package/src/components/ConnectionErrorView.tsx +272 -0
- package/src/components/ContextSwitcher.tsx +481 -0
- package/src/components/DebugOverlay.tsx +94 -0
- package/src/components/UserMenu.tsx +87 -0
- package/src/components/audit/AuditSettingsDialog.tsx +162 -0
- package/src/components/audit/AuditView.tsx +123 -0
- package/src/components/cost/CostTrendChart.tsx +388 -0
- package/src/components/cost/CostView.tsx +545 -0
- package/src/components/dock/BottomDock.tsx +96 -0
- package/src/components/dock/DockContext.tsx +11 -0
- package/src/components/dock/LocalTerminalTab.tsx +22 -0
- package/src/components/dock/LogsTab.tsx +26 -0
- package/src/components/dock/NodeTerminalTab.tsx +50 -0
- package/src/components/dock/TerminalTab.tsx +42 -0
- package/src/components/dock/TrafficFlowListTab.tsx +18 -0
- package/src/components/dock/WorkloadLogsTab.tsx +23 -0
- package/src/components/dock/index.ts +2 -0
- package/src/components/gitops/GitOpsActions.tsx +1 -0
- package/src/components/gitops/GitOpsStatusBadge.tsx +1 -0
- package/src/components/gitops/ManagedResourcesList.tsx +1 -0
- package/src/components/gitops/SyncCountdown.tsx +1 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/helm/ChartBrowser.tsx +580 -0
- package/src/components/helm/HelmReleaseDrawer.tsx +774 -0
- package/src/components/helm/HelmView.tsx +475 -0
- package/src/components/helm/InstallWizard.tsx +1060 -0
- package/src/components/helm/ManifestDiffViewer.tsx +91 -0
- package/src/components/helm/ManifestViewer.tsx +61 -0
- package/src/components/helm/OwnedResources.tsx +465 -0
- package/src/components/helm/RevisionHistory.tsx +167 -0
- package/src/components/helm/ValuesDiffPreview.tsx +190 -0
- package/src/components/helm/ValuesViewer.tsx +365 -0
- package/src/components/helm/helm-utils.ts +37 -0
- package/src/components/home/ActivitySummary.tsx +262 -0
- package/src/components/home/CertificateHealthCard.tsx +105 -0
- package/src/components/home/ClusterHealthCard.tsx +483 -0
- package/src/components/home/CostCard.tsx +112 -0
- package/src/components/home/HealthRing.tsx +1 -0
- package/src/components/home/HelmSummary.tsx +129 -0
- package/src/components/home/HomeView.tsx +224 -0
- package/src/components/home/MCPSetupDialog.tsx +417 -0
- package/src/components/home/NetworkPolicyCoverageCard.tsx +109 -0
- package/src/components/home/TopologyPreview.tsx +219 -0
- package/src/components/home/TrafficSummary.tsx +154 -0
- package/src/components/logs/JsonLogLine.tsx +1 -0
- package/src/components/logs/LogCore.tsx +2 -0
- package/src/components/logs/LogsViewer.tsx +44 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +40 -0
- package/src/components/logs/useLogBuffer.ts +2 -0
- package/src/components/logs/useLogSearch.ts +1 -0
- package/src/components/portforward/PortForwardButton.tsx +375 -0
- package/src/components/portforward/PortForwardManager.tsx +871 -0
- package/src/components/resource/PrometheusCharts.tsx +687 -0
- package/src/components/resource-drawer/ResourceDrawer.tsx +214 -0
- package/src/components/resources/ImageFilesystemModal.tsx +745 -0
- package/src/components/resources/PodFilesystemModal.tsx +407 -0
- package/src/components/resources/ResourceDetailDrawer.tsx +43 -0
- package/src/components/resources/ResourcesView.tsx +190 -0
- package/src/components/resources/drawer-components.tsx +1 -0
- package/src/components/resources/file-browser-utils.ts +35 -0
- package/src/components/resources/renderers/AlertRenderer.tsx +1 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +17 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/CertificateRenderer.tsx +1 -0
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +1 -0
- package/src/components/resources/renderers/ChallengeRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +1 -0
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +1 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +1 -0
- package/src/components/resources/renderers/EventRenderer.tsx +1 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +1 -0
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/GatewayRenderer.tsx +1 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +1 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/HPARenderer.tsx +1 -0
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +1 -0
- package/src/components/resources/renderers/JobRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +1 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +1 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/LeaseRenderer.tsx +1 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +1 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +44 -0
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/OrderRenderer.tsx +1 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +1 -0
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodRenderer.tsx +94 -0
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +1 -0
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +1 -0
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +1 -0
- package/src/components/resources/renderers/RoleRenderer.tsx +1 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +1 -0
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/SbomReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +26 -0
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/StorageClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/VPARenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +1 -0
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +52 -0
- package/src/components/resources/renderers/argo-cells.tsx +1 -0
- package/src/components/resources/renderers/certmanager-cells.tsx +1 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +1 -0
- package/src/components/resources/renderers/eso-cells.tsx +1 -0
- package/src/components/resources/renderers/flux-cells.tsx +1 -0
- package/src/components/resources/renderers/index.ts +91 -0
- package/src/components/resources/renderers/istio-cells.tsx +1 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +1 -0
- package/src/components/resources/renderers/keda-cells.tsx +1 -0
- package/src/components/resources/renderers/knative-cells.tsx +1 -0
- package/src/components/resources/renderers/kyverno-cells.tsx +1 -0
- package/src/components/resources/renderers/prometheus-cells.tsx +1 -0
- package/src/components/resources/renderers/traefik-cells.tsx +1 -0
- package/src/components/resources/renderers/trivy-cells.tsx +1 -0
- package/src/components/resources/renderers/trivy-shared.tsx +1 -0
- package/src/components/resources/renderers/velero-cells.tsx +1 -0
- package/src/components/resources/resource-utils-argo.ts +2 -0
- package/src/components/resources/resource-utils-certmanager.ts +2 -0
- package/src/components/resources/resource-utils-cnpg.ts +2 -0
- package/src/components/resources/resource-utils-eso.ts +2 -0
- package/src/components/resources/resource-utils-flux.ts +2 -0
- package/src/components/resources/resource-utils-istio.ts +2 -0
- package/src/components/resources/resource-utils-karpenter.ts +2 -0
- package/src/components/resources/resource-utils-keda.ts +2 -0
- package/src/components/resources/resource-utils-knative.ts +2 -0
- package/src/components/resources/resource-utils-kyverno.ts +2 -0
- package/src/components/resources/resource-utils-prometheus.ts +2 -0
- package/src/components/resources/resource-utils-traefik.ts +1 -0
- package/src/components/resources/resource-utils-trivy.ts +2 -0
- package/src/components/resources/resource-utils-velero.ts +2 -0
- package/src/components/resources/resource-utils.ts +5 -0
- package/src/components/settings/SettingsDialog.tsx +537 -0
- package/src/components/shared/CreateResourceDialog.tsx +17 -0
- package/src/components/shared/EditableYamlView.tsx +24 -0
- package/src/components/shared/LargeClusterNamespacePicker.tsx +70 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +31 -0
- package/src/components/timeline/DiffViewer.tsx +1 -0
- package/src/components/timeline/TimelineList.tsx +69 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +1308 -0
- package/src/components/timeline/TimelineView.tsx +157 -0
- package/src/components/timeline/shared.tsx +1 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +571 -0
- package/src/components/traffic/TrafficFlowList.tsx +415 -0
- package/src/components/traffic/TrafficFlowListContext.tsx +68 -0
- package/src/components/traffic/TrafficGraph.tsx +1546 -0
- package/src/components/traffic/TrafficView.tsx +1213 -0
- package/src/components/traffic/TrafficWizard.tsx +386 -0
- package/src/components/traffic/index.ts +3 -0
- package/src/components/ui/CodeViewer.tsx +8 -0
- package/src/components/ui/CommandPalette.tsx +460 -0
- package/src/components/ui/ConfirmDialog.tsx +1 -0
- package/src/components/ui/DiagnosticsOverlay.tsx +619 -0
- package/src/components/ui/ErrorBoundary.tsx +46 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +1 -0
- package/src/components/ui/Markdown.tsx +108 -0
- package/src/components/ui/MetricsChart.tsx +1 -0
- package/src/components/ui/NamespaceSelector.tsx +436 -0
- package/src/components/ui/ResourceBar.tsx +1 -0
- package/src/components/ui/ShortcutHelpOverlay.tsx +301 -0
- package/src/components/ui/Toast.tsx +1 -0
- package/src/components/ui/Tooltip.tsx +1 -0
- package/src/components/ui/UpdateNotification.tsx +299 -0
- package/src/components/ui/YamlEditor.tsx +1 -0
- package/src/components/workload/WorkloadView.tsx +532 -0
- package/src/context/ConnectionContext.tsx +173 -0
- package/src/context/ContextSwitchContext.tsx +56 -0
- package/src/context/NavCustomization.tsx +62 -0
- package/src/context/ThemeContext.tsx +97 -0
- package/src/contexts/CapabilitiesContext.tsx +130 -0
- package/src/hooks/useAnimatedUnmount.ts +1 -0
- package/src/hooks/useDesktopDownload.ts +41 -0
- package/src/hooks/useEventSource.ts +262 -0
- package/src/hooks/useFavorites.ts +69 -0
- package/src/hooks/useKeyboardShortcuts.tsx +7 -0
- package/src/hooks/useRefreshAnimation.ts +1 -0
- package/src/index.css +243 -0
- package/src/index.ts +17 -0
- package/src/main.tsx +158 -0
- package/src/types/gitops.ts +2 -0
- package/src/types.ts +3 -0
- package/src/utils/animation.ts +2 -0
- package/src/utils/badge-colors.ts +2 -0
- package/src/utils/context-name.ts +2 -0
- package/src/utils/desktop-download.ts +66 -0
- package/src/utils/desktop-open-folder.ts +21 -0
- package/src/utils/format.ts +2 -0
- package/src/utils/log-format.ts +12 -0
- package/src/utils/navigation.ts +23 -0
- package/src/utils/resource-hierarchy.ts +2 -0
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/skeleton-yaml.ts +2 -0
- package/src/utils/traffic-colors.ts +54 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
import { flushSync } from 'react-dom'
|
|
3
|
+
import { TRANSITION_DRAWER } from '../../utils/animation'
|
|
4
|
+
import { useRefreshAnimation } from '../../hooks/useRefreshAnimation'
|
|
5
|
+
import { X, Copy, Check, RefreshCw, Package, Code, History, FileText, Settings, Link2, Anchor, GitFork, BookOpen, ArrowUpCircle, Trash2 } from 'lucide-react'
|
|
6
|
+
import { clsx } from 'clsx'
|
|
7
|
+
import { useHelmRelease, useHelmManifest, useHelmValues, useHelmManifestDiff, useHelmUpgradeInfo, useHelmUninstall, upgradeWithProgress, rollbackWithProgress } from '../../api/client'
|
|
8
|
+
import { useQueryClient } from '@tanstack/react-query'
|
|
9
|
+
import { ConfirmDialog } from '../ui/ConfirmDialog'
|
|
10
|
+
import { Markdown } from '../ui/Markdown'
|
|
11
|
+
import type { SelectedHelmRelease, HelmHook, ChartDependency } from '../../types'
|
|
12
|
+
import type { NavigateToResource } from '../../utils/navigation'
|
|
13
|
+
import { formatDate } from './helm-utils'
|
|
14
|
+
import { getHelmStatusColor, SEVERITY_BADGE, SEVERITY_TEXT } from '../../utils/badge-colors'
|
|
15
|
+
import { useCanHelmWrite } from '../../contexts/CapabilitiesContext'
|
|
16
|
+
import { RevisionHistory } from './RevisionHistory'
|
|
17
|
+
import { ManifestViewer } from './ManifestViewer'
|
|
18
|
+
import { ValuesViewer } from './ValuesViewer'
|
|
19
|
+
import { OwnedResources } from './OwnedResources'
|
|
20
|
+
import { ManifestDiffViewer } from './ManifestDiffViewer'
|
|
21
|
+
|
|
22
|
+
interface HelmReleaseDrawerProps {
|
|
23
|
+
release: SelectedHelmRelease
|
|
24
|
+
onClose: () => void
|
|
25
|
+
onNavigateToResource?: NavigateToResource
|
|
26
|
+
/** Controls slide-in/out animation (driven by useAnimatedUnmount) */
|
|
27
|
+
isOpen?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type TabId = 'overview' | 'history' | 'manifest' | 'values' | 'resources' | 'hooks' | 'diff'
|
|
31
|
+
|
|
32
|
+
const MIN_WIDTH = 500
|
|
33
|
+
const MAX_WIDTH_PERCENT = 0.8
|
|
34
|
+
const DEFAULT_WIDTH = 1000
|
|
35
|
+
|
|
36
|
+
export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOpen = true }: HelmReleaseDrawerProps) {
|
|
37
|
+
const [activeTab, setActiveTab] = useState<TabId>('overview')
|
|
38
|
+
const [copied, setCopied] = useState<string | null>(null)
|
|
39
|
+
const [drawerWidth, setDrawerWidth] = useState(DEFAULT_WIDTH)
|
|
40
|
+
const [isResizing, setIsResizing] = useState(false)
|
|
41
|
+
const [selectedRevision, setSelectedRevision] = useState<number | undefined>(undefined)
|
|
42
|
+
const [showAllValues, setShowAllValues] = useState(false)
|
|
43
|
+
const [diffRevisions, setDiffRevisions] = useState<{ rev1: number; rev2: number } | null>(null)
|
|
44
|
+
const [rollbackRevision, setRollbackRevision] = useState<number | null>(null)
|
|
45
|
+
const [showUninstallConfirm, setShowUninstallConfirm] = useState(false)
|
|
46
|
+
const [showUpgradeConfirm, setShowUpgradeConfirm] = useState(false)
|
|
47
|
+
const resizeStartX = useRef(0)
|
|
48
|
+
const resizeStartWidth = useRef(DEFAULT_WIDTH)
|
|
49
|
+
const canHelmWrite = useCanHelmWrite()
|
|
50
|
+
|
|
51
|
+
const { data: releaseDetail, isLoading, refetch: refetchRelease } = useHelmRelease(
|
|
52
|
+
release.namespace,
|
|
53
|
+
release.name
|
|
54
|
+
)
|
|
55
|
+
const [refetch, isRefreshAnimating] = useRefreshAnimation(refetchRelease)
|
|
56
|
+
|
|
57
|
+
// Fetch manifest for selected revision (or latest)
|
|
58
|
+
const { data: manifest, isLoading: manifestLoading } = useHelmManifest(
|
|
59
|
+
release.namespace,
|
|
60
|
+
release.name,
|
|
61
|
+
selectedRevision
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// Fetch values
|
|
65
|
+
const { data: values, isLoading: valuesLoading } = useHelmValues(
|
|
66
|
+
release.namespace,
|
|
67
|
+
release.name,
|
|
68
|
+
showAllValues
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// Fetch diff if comparing revisions
|
|
72
|
+
const { data: diffData, isLoading: diffLoading } = useHelmManifestDiff(
|
|
73
|
+
release.namespace,
|
|
74
|
+
release.name,
|
|
75
|
+
diffRevisions?.rev1 || 0,
|
|
76
|
+
diffRevisions?.rev2 || 0
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// Lazy check for upgrade availability
|
|
80
|
+
const { data: upgradeInfo, isLoading: upgradeLoading } = useHelmUpgradeInfo(
|
|
81
|
+
release.namespace,
|
|
82
|
+
release.name
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Mutations for actions
|
|
86
|
+
const uninstallMutation = useHelmUninstall()
|
|
87
|
+
const queryClient = useQueryClient()
|
|
88
|
+
const [upgradeProgress, setUpgradeProgress] = useState<{ phase: string; message: string }[]>([])
|
|
89
|
+
const [isUpgrading, setIsUpgrading] = useState(false)
|
|
90
|
+
const [rollbackProgress, setRollbackProgress] = useState<{ phase: string; message: string }[]>([])
|
|
91
|
+
const [isRollingBack, setIsRollingBack] = useState(false)
|
|
92
|
+
|
|
93
|
+
// ESC key handler
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
96
|
+
if (event.key === 'Escape') onClose()
|
|
97
|
+
}
|
|
98
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
99
|
+
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
100
|
+
}, [onClose])
|
|
101
|
+
|
|
102
|
+
// Resize handlers
|
|
103
|
+
const handleResizeStart = useCallback((e: React.MouseEvent) => {
|
|
104
|
+
e.preventDefault()
|
|
105
|
+
setIsResizing(true)
|
|
106
|
+
resizeStartX.current = e.clientX
|
|
107
|
+
resizeStartWidth.current = drawerWidth
|
|
108
|
+
}, [drawerWidth])
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!isResizing) return
|
|
112
|
+
|
|
113
|
+
document.body.style.cursor = 'ew-resize'
|
|
114
|
+
document.body.style.userSelect = 'none'
|
|
115
|
+
|
|
116
|
+
const maxWidth = window.innerWidth * MAX_WIDTH_PERCENT
|
|
117
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
118
|
+
const deltaX = resizeStartX.current - e.clientX
|
|
119
|
+
const newWidth = resizeStartWidth.current + deltaX
|
|
120
|
+
setDrawerWidth(Math.max(MIN_WIDTH, Math.min(newWidth, maxWidth)))
|
|
121
|
+
}
|
|
122
|
+
const handleMouseUp = () => setIsResizing(false)
|
|
123
|
+
document.addEventListener('mousemove', handleMouseMove)
|
|
124
|
+
document.addEventListener('mouseup', handleMouseUp)
|
|
125
|
+
return () => {
|
|
126
|
+
document.body.style.cursor = ''
|
|
127
|
+
document.body.style.userSelect = ''
|
|
128
|
+
document.removeEventListener('mousemove', handleMouseMove)
|
|
129
|
+
document.removeEventListener('mouseup', handleMouseUp)
|
|
130
|
+
}
|
|
131
|
+
}, [isResizing])
|
|
132
|
+
|
|
133
|
+
const copyToClipboard = useCallback((text: string, key: string) => {
|
|
134
|
+
navigator.clipboard.writeText(text)
|
|
135
|
+
setCopied(key)
|
|
136
|
+
setTimeout(() => setCopied(null), 2000)
|
|
137
|
+
}, [])
|
|
138
|
+
|
|
139
|
+
const switchTab = useCallback((tab: TabId) => {
|
|
140
|
+
const update = () => flushSync(() => setActiveTab(tab))
|
|
141
|
+
if (document.startViewTransition) {
|
|
142
|
+
document.startViewTransition(update)
|
|
143
|
+
} else {
|
|
144
|
+
setActiveTab(tab)
|
|
145
|
+
}
|
|
146
|
+
}, [])
|
|
147
|
+
|
|
148
|
+
const handleCompareRevisions = (rev1: number, rev2: number) => {
|
|
149
|
+
setDiffRevisions({ rev1, rev2 })
|
|
150
|
+
switchTab('diff')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const handleViewRevision = (revision: number) => {
|
|
154
|
+
setSelectedRevision(revision)
|
|
155
|
+
switchTab('manifest')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const handleRollbackRequest = (revision: number) => {
|
|
159
|
+
setRollbackRevision(revision)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const handleRollbackConfirm = async () => {
|
|
163
|
+
if (rollbackRevision === null) return
|
|
164
|
+
setIsRollingBack(true)
|
|
165
|
+
setRollbackProgress([])
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await rollbackWithProgress(
|
|
169
|
+
release.namespace,
|
|
170
|
+
release.name,
|
|
171
|
+
rollbackRevision,
|
|
172
|
+
(event) => {
|
|
173
|
+
if (event.type === 'progress' && event.message) {
|
|
174
|
+
setRollbackProgress(prev => [...prev, {
|
|
175
|
+
phase: event.phase || 'progress',
|
|
176
|
+
message: event.message || '',
|
|
177
|
+
}])
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
setRollbackProgress(prev => [...prev, {
|
|
183
|
+
phase: 'complete',
|
|
184
|
+
message: `Successfully rolled back to revision ${rollbackRevision}`,
|
|
185
|
+
}])
|
|
186
|
+
|
|
187
|
+
queryClient.invalidateQueries({ queryKey: ['helm-releases'] })
|
|
188
|
+
queryClient.invalidateQueries({ queryKey: ['helm-release', release.namespace, release.name] })
|
|
189
|
+
|
|
190
|
+
setTimeout(() => {
|
|
191
|
+
setRollbackRevision(null)
|
|
192
|
+
setRollbackProgress([])
|
|
193
|
+
refetch()
|
|
194
|
+
switchTab('resources')
|
|
195
|
+
}, 1500)
|
|
196
|
+
} catch (err) {
|
|
197
|
+
setRollbackProgress(prev => [...prev, {
|
|
198
|
+
phase: 'error',
|
|
199
|
+
message: err instanceof Error ? err.message : 'Rollback failed',
|
|
200
|
+
}])
|
|
201
|
+
} finally {
|
|
202
|
+
setIsRollingBack(false)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const handleUninstallConfirm = () => {
|
|
207
|
+
uninstallMutation.mutate(
|
|
208
|
+
{ namespace: release.namespace, name: release.name },
|
|
209
|
+
{
|
|
210
|
+
onSuccess: () => {
|
|
211
|
+
setShowUninstallConfirm(false)
|
|
212
|
+
onClose()
|
|
213
|
+
},
|
|
214
|
+
onError: () => {
|
|
215
|
+
// Keep dialog open on error so user can see the error state
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const handleUpgradeConfirm = async () => {
|
|
222
|
+
if (!upgradeInfo?.latestVersion) return
|
|
223
|
+
setIsUpgrading(true)
|
|
224
|
+
setUpgradeProgress([])
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
await upgradeWithProgress(
|
|
228
|
+
release.namespace,
|
|
229
|
+
release.name,
|
|
230
|
+
upgradeInfo.latestVersion,
|
|
231
|
+
(event) => {
|
|
232
|
+
if (event.type === 'progress' && event.message) {
|
|
233
|
+
setUpgradeProgress(prev => [...prev, {
|
|
234
|
+
phase: event.phase || 'progress',
|
|
235
|
+
message: event.message || '',
|
|
236
|
+
}])
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
setUpgradeProgress(prev => [...prev, {
|
|
242
|
+
phase: 'complete',
|
|
243
|
+
message: `Successfully upgraded to ${upgradeInfo.latestVersion}`,
|
|
244
|
+
}])
|
|
245
|
+
|
|
246
|
+
// Invalidate queries
|
|
247
|
+
queryClient.invalidateQueries({ queryKey: ['helm-releases'] })
|
|
248
|
+
queryClient.invalidateQueries({ queryKey: ['helm-release', release.namespace, release.name] })
|
|
249
|
+
queryClient.invalidateQueries({ queryKey: ['helm-upgrade-info', release.namespace, release.name] })
|
|
250
|
+
queryClient.invalidateQueries({ queryKey: ['helm-batch-upgrade-info'] })
|
|
251
|
+
|
|
252
|
+
setTimeout(() => {
|
|
253
|
+
setShowUpgradeConfirm(false)
|
|
254
|
+
setUpgradeProgress([])
|
|
255
|
+
refetch()
|
|
256
|
+
switchTab('resources')
|
|
257
|
+
}, 1500)
|
|
258
|
+
} catch (err) {
|
|
259
|
+
setUpgradeProgress(prev => [...prev, {
|
|
260
|
+
phase: 'error',
|
|
261
|
+
message: err instanceof Error ? err.message : 'Upgrade failed',
|
|
262
|
+
}])
|
|
263
|
+
} finally {
|
|
264
|
+
setIsUpgrading(false)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const headerHeight = 49
|
|
269
|
+
|
|
270
|
+
const tabs: { id: TabId; label: string; icon: typeof Package }[] = [
|
|
271
|
+
{ id: 'overview', label: 'Overview', icon: Package },
|
|
272
|
+
{ id: 'history', label: 'History', icon: History },
|
|
273
|
+
{ id: 'manifest', label: 'Manifest', icon: Code },
|
|
274
|
+
{ id: 'values', label: 'Values', icon: Settings },
|
|
275
|
+
{ id: 'resources', label: 'Resources', icon: Link2 },
|
|
276
|
+
{ id: 'hooks', label: 'Hooks', icon: Anchor },
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
// Add diff tab only when comparing
|
|
280
|
+
if (diffRevisions) {
|
|
281
|
+
tabs.push({ id: 'diff', label: 'Diff', icon: FileText })
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div
|
|
286
|
+
className={clsx(
|
|
287
|
+
'fixed right-0 bg-theme-surface border-l border-theme-border flex flex-col shadow-drawer z-40',
|
|
288
|
+
TRANSITION_DRAWER,
|
|
289
|
+
isOpen ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'
|
|
290
|
+
)}
|
|
291
|
+
style={{ width: drawerWidth, top: headerHeight, height: `calc(100vh - ${headerHeight}px)` }}
|
|
292
|
+
>
|
|
293
|
+
{/* Resize handle */}
|
|
294
|
+
<div
|
|
295
|
+
onMouseDown={handleResizeStart}
|
|
296
|
+
className={clsx(
|
|
297
|
+
'absolute left-0 top-0 bottom-0 w-2 cursor-ew-resize z-10 hover:bg-blue-500/50 transition-colors',
|
|
298
|
+
'hidden sm:block',
|
|
299
|
+
isResizing && 'bg-blue-500/50'
|
|
300
|
+
)}
|
|
301
|
+
/>
|
|
302
|
+
|
|
303
|
+
{/* Header */}
|
|
304
|
+
<div className="border-b border-theme-border shrink-0">
|
|
305
|
+
<div className="flex items-center justify-between px-4 pt-3 pb-2">
|
|
306
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
307
|
+
<span className={clsx('badge', SEVERITY_BADGE.info)}>
|
|
308
|
+
Helm Release
|
|
309
|
+
</span>
|
|
310
|
+
{releaseDetail && (
|
|
311
|
+
<span className={clsx('badge', getHelmStatusColor(releaseDetail.status))}>
|
|
312
|
+
{releaseDetail.status}
|
|
313
|
+
</span>
|
|
314
|
+
)}
|
|
315
|
+
{/* Upgrade indicator */}
|
|
316
|
+
{upgradeLoading ? (
|
|
317
|
+
<span className="badge bg-theme-hover/50 text-theme-text-secondary animate-pulse">
|
|
318
|
+
checking...
|
|
319
|
+
</span>
|
|
320
|
+
) : upgradeInfo?.updateAvailable ? (
|
|
321
|
+
<button
|
|
322
|
+
onClick={() => setShowUpgradeConfirm(true)}
|
|
323
|
+
disabled={!canHelmWrite}
|
|
324
|
+
className={clsx(
|
|
325
|
+
'badge transition-colors', SEVERITY_BADGE.warning,
|
|
326
|
+
canHelmWrite ? 'hover:bg-amber-500/30 cursor-pointer' : 'opacity-50 cursor-not-allowed'
|
|
327
|
+
)}
|
|
328
|
+
title={canHelmWrite ? `Click to upgrade: ${upgradeInfo.currentVersion} → ${upgradeInfo.latestVersion}${upgradeInfo.repositoryName ? ` (${upgradeInfo.repositoryName})` : ''}` : 'Helm write permissions required (rbac.helm=true)'}
|
|
329
|
+
>
|
|
330
|
+
<ArrowUpCircle className="w-3 h-3" />
|
|
331
|
+
{upgradeInfo.latestVersion}
|
|
332
|
+
</button>
|
|
333
|
+
) : upgradeInfo && !upgradeInfo.error ? (
|
|
334
|
+
<span className={clsx('badge', SEVERITY_BADGE.success)} title="Chart is up to date">
|
|
335
|
+
latest
|
|
336
|
+
</span>
|
|
337
|
+
) : null}
|
|
338
|
+
</div>
|
|
339
|
+
<div className="flex items-center gap-1">
|
|
340
|
+
<button
|
|
341
|
+
onClick={refetch}
|
|
342
|
+
disabled={isRefreshAnimating}
|
|
343
|
+
className="p-1.5 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded disabled:opacity-50"
|
|
344
|
+
title="Refresh"
|
|
345
|
+
>
|
|
346
|
+
<RefreshCw className={clsx('w-4 h-4', isRefreshAnimating && 'animate-spin')} />
|
|
347
|
+
</button>
|
|
348
|
+
<button
|
|
349
|
+
onClick={() => setShowUninstallConfirm(true)}
|
|
350
|
+
disabled={!canHelmWrite}
|
|
351
|
+
className={clsx(
|
|
352
|
+
'p-1.5 rounded',
|
|
353
|
+
canHelmWrite
|
|
354
|
+
? 'text-theme-text-secondary hover:text-red-400 hover:bg-red-500/10'
|
|
355
|
+
: 'text-theme-text-disabled cursor-not-allowed'
|
|
356
|
+
)}
|
|
357
|
+
title={canHelmWrite ? 'Uninstall release' : 'Helm write permissions required (rbac.helm=true)'}
|
|
358
|
+
>
|
|
359
|
+
<Trash2 className="w-4 h-4" />
|
|
360
|
+
</button>
|
|
361
|
+
<button onClick={onClose} className="p-1.5 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded" title="Close (Esc)">
|
|
362
|
+
<X className="w-4 h-4" />
|
|
363
|
+
</button>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
|
|
367
|
+
{/* Name and namespace */}
|
|
368
|
+
<div className="px-4 pb-3">
|
|
369
|
+
<div className="flex items-center gap-2">
|
|
370
|
+
<Package className="w-5 h-5 text-purple-400" />
|
|
371
|
+
<h2 className="text-lg font-semibold text-theme-text-primary truncate">{release.name}</h2>
|
|
372
|
+
<button
|
|
373
|
+
onClick={() => copyToClipboard(release.name, 'name')}
|
|
374
|
+
className="p-1 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded shrink-0"
|
|
375
|
+
title="Copy name"
|
|
376
|
+
>
|
|
377
|
+
{copied === 'name' ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
|
|
378
|
+
</button>
|
|
379
|
+
</div>
|
|
380
|
+
<p className="text-sm text-theme-text-tertiary">{release.namespace}</p>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
{/* Tabs */}
|
|
384
|
+
<div className="flex items-center gap-1 px-4 pb-2 overflow-x-auto">
|
|
385
|
+
{tabs.map((tab) => (
|
|
386
|
+
<button
|
|
387
|
+
key={tab.id}
|
|
388
|
+
onClick={() => switchTab(tab.id)}
|
|
389
|
+
className={clsx(
|
|
390
|
+
'flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors whitespace-nowrap',
|
|
391
|
+
activeTab === tab.id
|
|
392
|
+
? 'bg-theme-elevated text-theme-text-primary'
|
|
393
|
+
: 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated/50'
|
|
394
|
+
)}
|
|
395
|
+
>
|
|
396
|
+
<tab.icon className="w-3.5 h-3.5" />
|
|
397
|
+
{tab.label}
|
|
398
|
+
</button>
|
|
399
|
+
))}
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
{/* Content */}
|
|
404
|
+
<div className="flex-1 overflow-y-auto" style={{ viewTransitionName: 'helm-drawer-content' }}>
|
|
405
|
+
{isLoading ? (
|
|
406
|
+
<div className="flex items-center justify-center h-32 text-theme-text-tertiary">Loading...</div>
|
|
407
|
+
) : !releaseDetail ? (
|
|
408
|
+
<div className="flex items-center justify-center h-32 text-theme-text-tertiary">Release not found</div>
|
|
409
|
+
) : (
|
|
410
|
+
<>
|
|
411
|
+
{activeTab === 'overview' && (
|
|
412
|
+
<OverviewTab release={releaseDetail} onCopy={copyToClipboard} copied={copied} />
|
|
413
|
+
)}
|
|
414
|
+
{activeTab === 'history' && (
|
|
415
|
+
<RevisionHistory
|
|
416
|
+
history={releaseDetail.history}
|
|
417
|
+
currentRevision={releaseDetail.revision}
|
|
418
|
+
onViewRevision={handleViewRevision}
|
|
419
|
+
onCompare={handleCompareRevisions}
|
|
420
|
+
onRollback={canHelmWrite ? handleRollbackRequest : undefined}
|
|
421
|
+
/>
|
|
422
|
+
)}
|
|
423
|
+
{activeTab === 'manifest' && (
|
|
424
|
+
<ManifestViewer
|
|
425
|
+
manifest={manifest || ''}
|
|
426
|
+
isLoading={manifestLoading}
|
|
427
|
+
revision={selectedRevision}
|
|
428
|
+
onCopy={(text) => copyToClipboard(text, 'manifest')}
|
|
429
|
+
copied={copied === 'manifest'}
|
|
430
|
+
/>
|
|
431
|
+
)}
|
|
432
|
+
{activeTab === 'values' && (
|
|
433
|
+
<ValuesViewer
|
|
434
|
+
values={values}
|
|
435
|
+
isLoading={valuesLoading}
|
|
436
|
+
showAllValues={showAllValues}
|
|
437
|
+
onToggleAllValues={setShowAllValues}
|
|
438
|
+
onCopy={(text) => copyToClipboard(text, 'values')}
|
|
439
|
+
copied={copied === 'values'}
|
|
440
|
+
namespace={release.namespace}
|
|
441
|
+
name={release.name}
|
|
442
|
+
onApplySuccess={() => refetch()}
|
|
443
|
+
/>
|
|
444
|
+
)}
|
|
445
|
+
{activeTab === 'resources' && (
|
|
446
|
+
<OwnedResources
|
|
447
|
+
resources={releaseDetail.resources}
|
|
448
|
+
onNavigate={onNavigateToResource}
|
|
449
|
+
/>
|
|
450
|
+
)}
|
|
451
|
+
{activeTab === 'hooks' && (
|
|
452
|
+
<HooksTab hooks={releaseDetail.hooks || []} />
|
|
453
|
+
)}
|
|
454
|
+
{activeTab === 'diff' && diffRevisions && (
|
|
455
|
+
<ManifestDiffViewer
|
|
456
|
+
diff={diffData?.diff || ''}
|
|
457
|
+
isLoading={diffLoading}
|
|
458
|
+
revision1={diffRevisions.rev1}
|
|
459
|
+
revision2={diffRevisions.rev2}
|
|
460
|
+
onClose={() => {
|
|
461
|
+
setDiffRevisions(null)
|
|
462
|
+
setActiveTab('history')
|
|
463
|
+
}}
|
|
464
|
+
/>
|
|
465
|
+
)}
|
|
466
|
+
</>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
{/* Rollback confirmation dialog */}
|
|
471
|
+
<ConfirmDialog
|
|
472
|
+
open={rollbackRevision !== null}
|
|
473
|
+
onClose={() => {
|
|
474
|
+
setRollbackRevision(null)
|
|
475
|
+
setRollbackProgress([])
|
|
476
|
+
if (isRollingBack) {
|
|
477
|
+
setIsRollingBack(false)
|
|
478
|
+
switchTab('resources')
|
|
479
|
+
}
|
|
480
|
+
}}
|
|
481
|
+
onConfirm={handleRollbackConfirm}
|
|
482
|
+
title="Rollback Release"
|
|
483
|
+
message={`Rollback "${release.name}" to revision ${rollbackRevision}?`}
|
|
484
|
+
details={rollbackProgress.length === 0
|
|
485
|
+
? `This will create a new revision that reverts the release to the state it was in at revision ${rollbackRevision}. The rollback will be applied to your cluster immediately.`
|
|
486
|
+
: undefined
|
|
487
|
+
}
|
|
488
|
+
confirmLabel="Rollback"
|
|
489
|
+
variant="warning"
|
|
490
|
+
isLoading={isRollingBack}
|
|
491
|
+
isClosable
|
|
492
|
+
>
|
|
493
|
+
{rollbackProgress.length > 0 && <ProgressLog entries={rollbackProgress} />}
|
|
494
|
+
</ConfirmDialog>
|
|
495
|
+
|
|
496
|
+
{/* Uninstall confirmation dialog */}
|
|
497
|
+
<ConfirmDialog
|
|
498
|
+
open={showUninstallConfirm}
|
|
499
|
+
onClose={() => setShowUninstallConfirm(false)}
|
|
500
|
+
onConfirm={handleUninstallConfirm}
|
|
501
|
+
title="Uninstall Release"
|
|
502
|
+
message={`Are you sure you want to uninstall "${release.name}"?`}
|
|
503
|
+
details={`This will remove the Helm release and all associated Kubernetes resources from the "${release.namespace}" namespace. This action cannot be undone.`}
|
|
504
|
+
confirmLabel="Uninstall"
|
|
505
|
+
variant="danger"
|
|
506
|
+
isLoading={uninstallMutation.isPending}
|
|
507
|
+
/>
|
|
508
|
+
|
|
509
|
+
{/* Upgrade confirmation dialog */}
|
|
510
|
+
<ConfirmDialog
|
|
511
|
+
open={showUpgradeConfirm}
|
|
512
|
+
onClose={() => {
|
|
513
|
+
setShowUpgradeConfirm(false)
|
|
514
|
+
setUpgradeProgress([])
|
|
515
|
+
if (isUpgrading) {
|
|
516
|
+
// Upgrade continues server-side — switch to resources tab to monitor
|
|
517
|
+
setIsUpgrading(false)
|
|
518
|
+
switchTab('resources')
|
|
519
|
+
}
|
|
520
|
+
}}
|
|
521
|
+
onConfirm={handleUpgradeConfirm}
|
|
522
|
+
title="Upgrade Release"
|
|
523
|
+
message={`Upgrade "${release.name}" to version ${upgradeInfo?.latestVersion}?`}
|
|
524
|
+
details={upgradeProgress.length === 0
|
|
525
|
+
? `This will upgrade the chart from version ${upgradeInfo?.currentVersion} to ${upgradeInfo?.latestVersion}. Your existing values will be preserved. The upgrade will be applied to your cluster immediately.`
|
|
526
|
+
: undefined
|
|
527
|
+
}
|
|
528
|
+
confirmLabel="Upgrade"
|
|
529
|
+
variant="warning"
|
|
530
|
+
isLoading={isUpgrading}
|
|
531
|
+
isClosable
|
|
532
|
+
>
|
|
533
|
+
{upgradeProgress.length > 0 && <ProgressLog entries={upgradeProgress} />}
|
|
534
|
+
</ConfirmDialog>
|
|
535
|
+
</div>
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Shared progress log for streaming Helm operations
|
|
540
|
+
function ProgressLog({ entries }: { entries: { phase: string; message: string }[] }) {
|
|
541
|
+
return (
|
|
542
|
+
<div className="space-y-1.5 max-h-48 overflow-auto">
|
|
543
|
+
{entries.map((log, i) => (
|
|
544
|
+
<div key={i} className="flex items-start gap-2 text-xs">
|
|
545
|
+
<span className={clsx(
|
|
546
|
+
'px-1.5 py-0.5 rounded font-medium shrink-0',
|
|
547
|
+
log.phase === 'error' ? SEVERITY_BADGE.error :
|
|
548
|
+
log.phase === 'complete' ? SEVERITY_BADGE.success :
|
|
549
|
+
SEVERITY_BADGE.info
|
|
550
|
+
)}>
|
|
551
|
+
{log.phase}
|
|
552
|
+
</span>
|
|
553
|
+
<span className={clsx(
|
|
554
|
+
log.phase === 'error' ? SEVERITY_TEXT.error :
|
|
555
|
+
log.phase === 'complete' ? SEVERITY_TEXT.success :
|
|
556
|
+
'text-theme-text-secondary'
|
|
557
|
+
)}>
|
|
558
|
+
{log.message}
|
|
559
|
+
</span>
|
|
560
|
+
</div>
|
|
561
|
+
))}
|
|
562
|
+
</div>
|
|
563
|
+
)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Overview tab content
|
|
567
|
+
interface OverviewTabProps {
|
|
568
|
+
release: {
|
|
569
|
+
chart: string
|
|
570
|
+
chartVersion: string
|
|
571
|
+
appVersion: string
|
|
572
|
+
revision: number
|
|
573
|
+
updated: string
|
|
574
|
+
description: string
|
|
575
|
+
notes: string
|
|
576
|
+
readme?: string
|
|
577
|
+
dependencies?: ChartDependency[]
|
|
578
|
+
}
|
|
579
|
+
onCopy: (text: string, key: string) => void
|
|
580
|
+
copied: string | null
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function OverviewTab({ release, onCopy, copied }: OverviewTabProps) {
|
|
584
|
+
return (
|
|
585
|
+
<div className="p-4 space-y-4">
|
|
586
|
+
{/* Chart info */}
|
|
587
|
+
<div className="bg-theme-elevated/30 rounded-lg p-4">
|
|
588
|
+
<h3 className="text-sm font-medium text-theme-text-secondary mb-3">Chart Information</h3>
|
|
589
|
+
<dl className="grid grid-cols-2 gap-3 text-sm">
|
|
590
|
+
<div>
|
|
591
|
+
<dt className="text-theme-text-tertiary">Chart</dt>
|
|
592
|
+
<dd className="text-theme-text-primary font-medium">{release.chart}</dd>
|
|
593
|
+
</div>
|
|
594
|
+
<div>
|
|
595
|
+
<dt className="text-theme-text-tertiary">Chart Version</dt>
|
|
596
|
+
<dd className="text-theme-text-primary">{release.chartVersion}</dd>
|
|
597
|
+
</div>
|
|
598
|
+
<div>
|
|
599
|
+
<dt className="text-theme-text-tertiary">App Version</dt>
|
|
600
|
+
<dd className="text-theme-text-primary">{release.appVersion || '-'}</dd>
|
|
601
|
+
</div>
|
|
602
|
+
<div>
|
|
603
|
+
<dt className="text-theme-text-tertiary">Revision</dt>
|
|
604
|
+
<dd className="text-theme-text-primary">{release.revision}</dd>
|
|
605
|
+
</div>
|
|
606
|
+
<div className="col-span-2">
|
|
607
|
+
<dt className="text-theme-text-tertiary">Updated</dt>
|
|
608
|
+
<dd className="text-theme-text-primary">{formatDate(release.updated)}</dd>
|
|
609
|
+
</div>
|
|
610
|
+
</dl>
|
|
611
|
+
</div>
|
|
612
|
+
|
|
613
|
+
{/* Description */}
|
|
614
|
+
{release.description && (
|
|
615
|
+
<div className="bg-theme-elevated/30 rounded-lg p-4">
|
|
616
|
+
<h3 className="text-sm font-medium text-theme-text-secondary mb-2">Description</h3>
|
|
617
|
+
<p className="text-sm text-theme-text-secondary">{release.description}</p>
|
|
618
|
+
</div>
|
|
619
|
+
)}
|
|
620
|
+
|
|
621
|
+
{/* Notes */}
|
|
622
|
+
{release.notes && (
|
|
623
|
+
<div className="bg-theme-elevated/30 rounded-lg p-4">
|
|
624
|
+
<div className="flex items-center justify-between mb-2">
|
|
625
|
+
<h3 className="text-sm font-medium text-theme-text-secondary">Release Notes</h3>
|
|
626
|
+
<button
|
|
627
|
+
onClick={() => onCopy(release.notes, 'notes')}
|
|
628
|
+
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"
|
|
629
|
+
>
|
|
630
|
+
{copied === 'notes' ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
|
|
631
|
+
Copy
|
|
632
|
+
</button>
|
|
633
|
+
</div>
|
|
634
|
+
<div className="text-xs bg-theme-base/50 rounded p-3 max-h-64 overflow-auto">
|
|
635
|
+
<Markdown>{release.notes}</Markdown>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
)}
|
|
639
|
+
|
|
640
|
+
{/* Dependencies */}
|
|
641
|
+
{release.dependencies && release.dependencies.length > 0 && (
|
|
642
|
+
<div className="bg-theme-elevated/30 rounded-lg p-4">
|
|
643
|
+
<div className="flex items-center gap-2 mb-3">
|
|
644
|
+
<GitFork className="w-4 h-4 text-theme-text-secondary" />
|
|
645
|
+
<h3 className="text-sm font-medium text-theme-text-secondary">Chart Dependencies</h3>
|
|
646
|
+
</div>
|
|
647
|
+
<div className="space-y-2">
|
|
648
|
+
{release.dependencies.map((dep) => (
|
|
649
|
+
<div key={dep.name} className="flex items-center justify-between bg-theme-base/50 rounded p-2 text-sm">
|
|
650
|
+
<div className="flex items-center gap-2">
|
|
651
|
+
<span className="text-theme-text-primary font-medium">{dep.name}</span>
|
|
652
|
+
<span className="text-theme-text-tertiary">{dep.version}</span>
|
|
653
|
+
</div>
|
|
654
|
+
<div className="flex items-center gap-2">
|
|
655
|
+
{dep.condition && (
|
|
656
|
+
<span className="text-xs text-theme-text-tertiary">{dep.condition}</span>
|
|
657
|
+
)}
|
|
658
|
+
<span className={clsx(
|
|
659
|
+
'badge-sm',
|
|
660
|
+
dep.enabled
|
|
661
|
+
? SEVERITY_BADGE.success
|
|
662
|
+
: SEVERITY_BADGE.neutral
|
|
663
|
+
)}>
|
|
664
|
+
{dep.enabled ? 'enabled' : 'disabled'}
|
|
665
|
+
</span>
|
|
666
|
+
</div>
|
|
667
|
+
</div>
|
|
668
|
+
))}
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
)}
|
|
672
|
+
|
|
673
|
+
{/* README */}
|
|
674
|
+
{release.readme && (
|
|
675
|
+
<div className="bg-theme-elevated/30 rounded-lg p-4">
|
|
676
|
+
<div className="flex items-center justify-between mb-2">
|
|
677
|
+
<div className="flex items-center gap-2">
|
|
678
|
+
<BookOpen className="w-4 h-4 text-theme-text-secondary" />
|
|
679
|
+
<h3 className="text-sm font-medium text-theme-text-secondary">Chart README</h3>
|
|
680
|
+
</div>
|
|
681
|
+
<button
|
|
682
|
+
onClick={() => onCopy(release.readme!, 'readme')}
|
|
683
|
+
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"
|
|
684
|
+
>
|
|
685
|
+
{copied === 'readme' ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
|
|
686
|
+
Copy
|
|
687
|
+
</button>
|
|
688
|
+
</div>
|
|
689
|
+
<div className="text-xs bg-theme-base/50 rounded p-3 max-h-96 overflow-auto">
|
|
690
|
+
<Markdown>{release.readme}</Markdown>
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
)}
|
|
694
|
+
</div>
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Hooks tab content
|
|
699
|
+
interface HooksTabProps {
|
|
700
|
+
hooks: HelmHook[]
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function HooksTab({ hooks }: HooksTabProps) {
|
|
704
|
+
if (hooks.length === 0) {
|
|
705
|
+
return (
|
|
706
|
+
<div className="flex flex-col items-center justify-center h-48 text-theme-text-tertiary">
|
|
707
|
+
<Anchor className="w-8 h-8 mb-2 opacity-50" />
|
|
708
|
+
<p>No hooks defined for this release</p>
|
|
709
|
+
</div>
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const getHookStatusColor = (status?: string) => {
|
|
714
|
+
if (!status) return SEVERITY_BADGE.neutral
|
|
715
|
+
switch (status.toLowerCase()) {
|
|
716
|
+
case 'succeeded':
|
|
717
|
+
return SEVERITY_BADGE.success
|
|
718
|
+
case 'failed':
|
|
719
|
+
return SEVERITY_BADGE.error
|
|
720
|
+
case 'running':
|
|
721
|
+
return SEVERITY_BADGE.info
|
|
722
|
+
default:
|
|
723
|
+
return SEVERITY_BADGE.neutral
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const getEventColor = (event: string) => {
|
|
728
|
+
if (event.includes('delete')) return SEVERITY_BADGE.error
|
|
729
|
+
if (event.includes('install')) return SEVERITY_BADGE.success
|
|
730
|
+
if (event.includes('upgrade')) return SEVERITY_BADGE.info
|
|
731
|
+
if (event.includes('rollback')) return SEVERITY_BADGE.warning
|
|
732
|
+
return SEVERITY_BADGE.neutral
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return (
|
|
736
|
+
<div className="p-4 space-y-3">
|
|
737
|
+
<p className="text-sm text-theme-text-secondary mb-4">
|
|
738
|
+
Helm hooks are executed at specific points during the release lifecycle.
|
|
739
|
+
</p>
|
|
740
|
+
{hooks.map((hook) => (
|
|
741
|
+
<div key={hook.name} className="bg-theme-elevated/30 rounded-lg p-4">
|
|
742
|
+
<div className="flex items-start justify-between mb-2">
|
|
743
|
+
<div>
|
|
744
|
+
<div className="flex items-center gap-2">
|
|
745
|
+
<span className="text-theme-text-primary font-medium">{hook.name}</span>
|
|
746
|
+
<span className="badge-sm bg-theme-hover/50 text-theme-text-secondary">
|
|
747
|
+
{hook.kind}
|
|
748
|
+
</span>
|
|
749
|
+
</div>
|
|
750
|
+
<div className="flex items-center gap-2 mt-1 text-xs text-theme-text-tertiary">
|
|
751
|
+
<span>Weight: {hook.weight}</span>
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
{hook.status && (
|
|
755
|
+
<span className={clsx('badge', getHookStatusColor(hook.status))}>
|
|
756
|
+
{hook.status}
|
|
757
|
+
</span>
|
|
758
|
+
)}
|
|
759
|
+
</div>
|
|
760
|
+
<div className="flex flex-wrap gap-1.5 mt-2">
|
|
761
|
+
{hook.events.map((event) => (
|
|
762
|
+
<span
|
|
763
|
+
key={event}
|
|
764
|
+
className={clsx('badge', getEventColor(event))}
|
|
765
|
+
>
|
|
766
|
+
{event}
|
|
767
|
+
</span>
|
|
768
|
+
))}
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
))}
|
|
772
|
+
</div>
|
|
773
|
+
)
|
|
774
|
+
}
|