@skyhook-io/radar-app 1.8.6 → 1.8.7
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 +7 -1
- package/package.json +11 -9
- package/src/App.tsx +221 -181
- package/src/RadarApp.tsx +91 -20
- package/src/api/client.delta.test.ts +89 -0
- package/src/api/client.deltaSync.test.ts +216 -0
- package/src/api/client.ts +262 -34
- package/src/api/diagnose.ts +288 -0
- package/src/api/timelineSource.test.ts +217 -0
- package/src/api/timelineSource.ts +580 -0
- package/src/components/ConnectionErrorView.tsx +174 -70
- package/src/components/ContextSwitcher.tsx +13 -5
- package/src/components/applications/ApplicationsView.tsx +327 -26
- package/src/components/audit/AuditSettingsDialog.tsx +2 -2
- package/src/components/curl/ServiceCurlButton.tsx +2 -2
- package/src/components/diagnose/AISettings.tsx +121 -0
- package/src/components/diagnose/DiagnoseContext.tsx +491 -0
- package/src/components/diagnose/DiagnoseSurface.tsx +385 -0
- package/src/components/diagnose/Home.tsx +163 -0
- package/src/components/diagnose/InvestigationView.tsx +604 -0
- package/src/components/diagnose/LocalDiagnoseAction.tsx +150 -0
- package/src/components/diagnose/launch.ts +65 -0
- package/src/components/diagnose/parts.tsx +1689 -0
- package/src/components/dock/BottomDock.tsx +2 -3
- package/src/components/dock/WorkloadLogsTab.tsx +21 -5
- package/src/components/execution/BatchExecutionView.test.ts +170 -0
- package/src/components/execution/BatchExecutionView.tsx +1329 -0
- package/src/components/execution/batch-run-actions.test.ts +48 -0
- package/src/components/execution/batch-run-actions.ts +24 -0
- package/src/components/execution/batch-timeline.test.ts +57 -0
- package/src/components/execution/batch-timeline.ts +46 -0
- package/src/components/execution/execution-definition.test.ts +208 -0
- package/src/components/execution/execution-definition.ts +245 -0
- package/src/components/helm/ChartBrowser.tsx +2 -3
- package/src/components/helm/HelmReleaseDrawer.test.ts +17 -0
- package/src/components/helm/HelmReleaseDrawer.tsx +376 -43
- package/src/components/helm/HelmView.tsx +2 -3
- package/src/components/helm/InstallWizard.tsx +3 -5
- package/src/components/helm/TrackChartSourceDialog.tsx +48 -4
- package/src/components/helm/ValuesDiffPreview.tsx +15 -4
- package/src/components/home/HomeView.tsx +17 -15
- package/src/components/home/MCPSetupDialog.tsx +1 -1
- package/src/components/home/mcpToolCatalog.ts +2 -2
- package/src/components/issues/IssuesPane.tsx +9 -1
- package/src/components/logs/ScheduledWorkloadLogsViewer.tsx +135 -0
- package/src/components/portforward/PortForwardButton.tsx +2 -2
- package/src/components/portforward/PortForwardManager.tsx +19 -14
- package/src/components/resource/PrometheusChartsGrid.tsx +6 -80
- package/src/components/resource/RightsizingStrip.tsx +0 -3
- package/src/components/resources/ImageFilesystemModal.tsx +2 -2
- package/src/components/resources/PodFilesystemModal.tsx +2 -3
- package/src/components/resources/ResourceDetailDrawer.tsx +2 -0
- package/src/components/resources/ResourcesView.tsx +13 -4
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +1 -0
- package/src/components/resources/renderers/index.ts +1 -0
- package/src/components/settings/SettingsDialog.tsx +80 -23
- package/src/components/shared/LargeClusterNamespacePicker.tsx +2 -2
- package/src/components/timeline/LocalTimelineScrubber.tsx +212 -0
- package/src/components/timeline/RetainedTimelineScrubber.tsx +311 -0
- package/src/components/timeline/TimelineList.tsx +51 -10
- package/src/components/timeline/TimelineView.tsx +701 -15
- package/src/components/timeline/TimelineView.urlparams.test.ts +294 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +2 -2
- package/src/components/traffic/TrafficView.tsx +8 -4
- package/src/components/ui/CommandPalette.tsx +2 -2
- package/src/components/workload/WorkloadView.tsx +319 -35
- package/src/context/ConnectionContext.tsx +109 -11
- package/src/context/DiagnoseCustomization.tsx +42 -0
- package/src/context/TimelineSource.tsx +50 -0
- package/src/hooks/useClusterLoadState.ts +73 -0
- package/src/hooks/useEventSource.ts +6 -0
- package/src/index.css +157 -0
- package/src/index.ts +12 -0
- package/src/types/clusterLoadState.ts +33 -0
- package/src/utils/navigation.ts +10 -0
|
@@ -4,15 +4,17 @@ import { FetchResult, useDockReservedHeight, compareVersions } from '@skyhook-io
|
|
|
4
4
|
import { startViewTransitionSafe } from '@skyhook-io/k8s-ui/utils/view-transition'
|
|
5
5
|
import { TRANSITION_DRAWER } from '../../utils/animation'
|
|
6
6
|
import { useRefreshAnimation } from '../../hooks/useRefreshAnimation'
|
|
7
|
-
import { X, Copy, Check, RefreshCw, Package, Code, History, Settings, Link2, Anchor, GitFork, BookOpen, ArrowUpCircle, Trash2, GitBranch, AlertTriangle, RotateCcw, Clock, GitCompare, ExternalLink } from 'lucide-react'
|
|
7
|
+
import { X, Copy, Check, RefreshCw, Package, Code, History, Settings, Link2, Anchor, GitFork, BookOpen, ArrowUpCircle, Trash2, GitBranch, AlertTriangle, RotateCcw, Clock, GitCompare, ExternalLink, ChevronRight, SlidersHorizontal, Eye, Loader2 } from 'lucide-react'
|
|
8
|
+
import yaml from 'yaml'
|
|
8
9
|
import { useNavigate } from 'react-router-dom'
|
|
9
10
|
import { clsx } from 'clsx'
|
|
10
|
-
import { useHelmRelease, useHelmManifest, useHelmValues, useHelmUpgradeInfo, useHelmReleaseVersions, useHelmUninstall, upgradeWithProgress, rollbackWithProgress } from '../../api/client'
|
|
11
|
+
import { useHelmRelease, useHelmManifest, useHelmValues, useHelmUpgradeInfo, useHelmReleaseVersions, useHelmUninstall, upgradeWithProgress, rollbackWithProgress, useHelmPreviewValues } from '../../api/client'
|
|
12
|
+
import { ValuesDiffPreview } from './ValuesDiffPreview'
|
|
11
13
|
import { useQueryClient } from '@tanstack/react-query'
|
|
12
14
|
import { ConfirmDialog } from '../ui/ConfirmDialog'
|
|
13
15
|
import { Tooltip } from '../ui/Tooltip'
|
|
14
16
|
import { Markdown } from '../ui/Markdown'
|
|
15
|
-
import type { SelectedHelmRelease, HelmHook, ChartDependency, HelmOperation, HelmOperationInsight, HelmOwnedResource, HookDiagnostic, HookLogEvidence } from '../../types'
|
|
17
|
+
import type { SelectedHelmRelease, HelmHook, ChartDependency, HelmOperation, HelmOperationInsight, HelmOwnedResource, HookDiagnostic, HookLogEvidence, UpgradeInfo, ValuesPreviewResponse } from '../../types'
|
|
16
18
|
import { apiVersionToGroup, kindToPlural, type NavigateToResource } from '../../utils/navigation'
|
|
17
19
|
import { formatDate } from './helm-utils'
|
|
18
20
|
import { getHelmStatusColor, getKindBadgeColor, getResourceStatusColor, SEVERITY_BADGE, SEVERITY_TEXT } from '../../utils/badge-colors'
|
|
@@ -30,15 +32,77 @@ interface HelmReleaseDrawerProps {
|
|
|
30
32
|
onNavigateToResource?: NavigateToResource
|
|
31
33
|
/** Controls slide-in/out animation (driven by useAnimatedUnmount) */
|
|
32
34
|
isOpen?: boolean
|
|
35
|
+
/** Right inset in px so the drawer sits beside a docked side panel (AI), not under it. */
|
|
36
|
+
rightInset?: number
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
type TabId = 'overview' | 'history' | 'manifest' | 'values' | 'resources' | 'hooks'
|
|
36
40
|
|
|
41
|
+
interface UpgradePreviewRequest {
|
|
42
|
+
targetVersion: string
|
|
43
|
+
values: Record<string, unknown>
|
|
44
|
+
repositoryName?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface ParsedUpgradeValues {
|
|
48
|
+
values: Record<string, unknown> | null
|
|
49
|
+
error: string | null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type UpgradeSourceIssue = NonNullable<UpgradeInfo['sourceIssue']>
|
|
53
|
+
type ActionableUpgradeSourceIssue = Exclude<UpgradeSourceIssue, 'ambiguous_repository'>
|
|
54
|
+
|
|
55
|
+
function getUpgradeSourceIssue(upgradeInfo: UpgradeInfo): UpgradeSourceIssue | undefined {
|
|
56
|
+
return upgradeInfo.sourceIssue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getUpgradeSourceIssueLabel(issue: UpgradeSourceIssue) {
|
|
60
|
+
switch (issue) {
|
|
61
|
+
case 'repo_index_error':
|
|
62
|
+
return 'upgrade source blocked'
|
|
63
|
+
case 'ambiguous_repository':
|
|
64
|
+
return 'upgrade source ambiguous'
|
|
65
|
+
case 'untracked':
|
|
66
|
+
return 'upgrade source not tracked'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getUpgradeSourceIssueTooltip(issue: UpgradeSourceIssue, error: string | undefined, canHelmWrite: boolean, disabledReason: string | undefined) {
|
|
71
|
+
if (!canHelmWrite) {
|
|
72
|
+
return disabledReason ?? error ?? 'Helm actions are disabled.'
|
|
73
|
+
}
|
|
74
|
+
switch (issue) {
|
|
75
|
+
case 'repo_index_error':
|
|
76
|
+
return 'A configured Helm repo index failed. Fix or refresh that repo, or register an OCI prefix if this chart came from OCI.'
|
|
77
|
+
case 'ambiguous_repository':
|
|
78
|
+
return 'Multiple configured Helm repos match this chart. Fix the repo list or source metadata so Radar can identify one source.'
|
|
79
|
+
case 'untracked':
|
|
80
|
+
return "Radar can't tell where this chart was installed from. Register an OCI chart source to track upgrades."
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseUpgradeValuesYaml(raw: string): ParsedUpgradeValues {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = yaml.parse(raw)
|
|
87
|
+
if (parsed === null || parsed === undefined) return { values: {}, error: null }
|
|
88
|
+
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
89
|
+
return { values: null, error: 'Values must be a YAML mapping (key: value), not a list or single value' }
|
|
90
|
+
}
|
|
91
|
+
return { values: parsed as Record<string, unknown>, error: null }
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return { values: null, error: err instanceof Error ? err.message : 'Invalid YAML' }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isUpgradeSourceIssueActionable(issue: UpgradeInfo['sourceIssue']): issue is ActionableUpgradeSourceIssue {
|
|
98
|
+
return Boolean(issue && issue !== 'ambiguous_repository')
|
|
99
|
+
}
|
|
100
|
+
|
|
37
101
|
const MIN_WIDTH = 500
|
|
38
102
|
const MAX_WIDTH_PERCENT = 0.8
|
|
39
103
|
const DEFAULT_WIDTH = 1000
|
|
40
104
|
|
|
41
|
-
export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOpen = true }: HelmReleaseDrawerProps) {
|
|
105
|
+
export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOpen = true, rightInset = 0 }: HelmReleaseDrawerProps) {
|
|
42
106
|
const navigate = useNavigate()
|
|
43
107
|
const [activeTab, setActiveTab] = useState<TabId>('overview')
|
|
44
108
|
const [copied, setCopied] = useState<string | null>(null)
|
|
@@ -52,8 +116,18 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
52
116
|
const [showUpgradeConfirm, setShowUpgradeConfirm] = useState(false)
|
|
53
117
|
const [showTrackSource, setShowTrackSource] = useState(false)
|
|
54
118
|
const [selectedVersion, setSelectedVersion] = useState<string | null>(null)
|
|
119
|
+
const [adjustValues, setAdjustValues] = useState(false)
|
|
120
|
+
const [renderAdjustValues, setRenderAdjustValues] = useState(false)
|
|
121
|
+
const [editedUpgradeYaml, setEditedUpgradeYaml] = useState('')
|
|
122
|
+
const [upgradeYamlError, setUpgradeYamlError] = useState<string | null>(null)
|
|
123
|
+
const [upgradeValuesSeeded, setUpgradeValuesSeeded] = useState(false)
|
|
124
|
+
const [upgradePreview, setUpgradePreview] = useState<ValuesPreviewResponse | null>(null)
|
|
125
|
+
const [upgradePreviewRequest, setUpgradePreviewRequest] = useState<UpgradePreviewRequest | null>(null)
|
|
126
|
+
const [showUpgradePreview, setShowUpgradePreview] = useState(false)
|
|
55
127
|
const resizeStartX = useRef(0)
|
|
56
128
|
const resizeStartWidth = useRef(DEFAULT_WIDTH)
|
|
129
|
+
const targetVersionRef = useRef('')
|
|
130
|
+
const editedUpgradeYamlRef = useRef('')
|
|
57
131
|
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct()
|
|
58
132
|
// Cloud viewers can't view release manifests / values / diffs
|
|
59
133
|
// (backend gate at requireCloudRole('member')). Skip the queries
|
|
@@ -85,6 +159,16 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
85
159
|
canViewSensitive,
|
|
86
160
|
selectedRevision,
|
|
87
161
|
)
|
|
162
|
+
const {
|
|
163
|
+
data: upgradeValues,
|
|
164
|
+
isLoading: upgradeValuesLoading,
|
|
165
|
+
error: upgradeValuesError,
|
|
166
|
+
} = useHelmValues(
|
|
167
|
+
helmNamespace,
|
|
168
|
+
release.name,
|
|
169
|
+
false,
|
|
170
|
+
canViewSensitive && showUpgradeConfirm && adjustValues,
|
|
171
|
+
)
|
|
88
172
|
|
|
89
173
|
// Lazy check for upgrade availability
|
|
90
174
|
const { data: upgradeInfo, isLoading: upgradeLoading, error: upgradeError } = useHelmUpgradeInfo(
|
|
@@ -92,11 +176,17 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
92
176
|
release.name
|
|
93
177
|
)
|
|
94
178
|
const upgradeErrorMessage = upgradeError instanceof Error ? upgradeError.message : 'Upgrade check failed'
|
|
179
|
+
const upgradeSourceIssue = upgradeInfo ? getUpgradeSourceIssue(upgradeInfo) : undefined
|
|
95
180
|
|
|
96
181
|
// Available versions for the upgrade dialog's picker — only fetched while the
|
|
97
182
|
// confirm dialog is open. Default the selection to latest when it opens.
|
|
98
|
-
const {
|
|
183
|
+
const {
|
|
184
|
+
data: availableVersions,
|
|
185
|
+
isLoading: availableVersionsLoading,
|
|
186
|
+
error: availableVersionsError,
|
|
187
|
+
} = useHelmReleaseVersions(helmNamespace, release.name, showUpgradeConfirm)
|
|
99
188
|
const targetVersion = selectedVersion ?? upgradeInfo?.latestVersion ?? ''
|
|
189
|
+
const selectableVersions = availableVersions && availableVersions.length > 1 ? availableVersions : null
|
|
100
190
|
// Semver compare, not list-position: the installed version may be older than
|
|
101
191
|
// the newest-N versions the picker shows, so it isn't always in the list.
|
|
102
192
|
const isDowngrade = Boolean(
|
|
@@ -106,12 +196,18 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
106
196
|
|
|
107
197
|
// Mutations for actions
|
|
108
198
|
const uninstallMutation = useHelmUninstall()
|
|
199
|
+
const upgradePreviewMutation = useHelmPreviewValues()
|
|
200
|
+
const isPreviewingUpgrade = upgradePreviewMutation.isPending
|
|
109
201
|
const queryClient = useQueryClient()
|
|
110
202
|
const [upgradeProgress, setUpgradeProgress] = useState<{ phase: string; message: string }[]>([])
|
|
111
203
|
const [isUpgrading, setIsUpgrading] = useState(false)
|
|
112
204
|
const [rollbackProgress, setRollbackProgress] = useState<{ phase: string; message: string }[]>([])
|
|
113
205
|
const [isRollingBack, setIsRollingBack] = useState(false)
|
|
114
206
|
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
targetVersionRef.current = targetVersion
|
|
209
|
+
}, [targetVersion])
|
|
210
|
+
|
|
115
211
|
// ESC key handler
|
|
116
212
|
useEffect(() => {
|
|
117
213
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
@@ -257,8 +353,90 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
257
353
|
)
|
|
258
354
|
}
|
|
259
355
|
|
|
260
|
-
const
|
|
261
|
-
|
|
356
|
+
const resetUpgradeDialog = () => {
|
|
357
|
+
setUpgradeProgress([])
|
|
358
|
+
setSelectedVersion(null)
|
|
359
|
+
setAdjustValues(false)
|
|
360
|
+
setRenderAdjustValues(false)
|
|
361
|
+
setEditedUpgradeYaml('')
|
|
362
|
+
editedUpgradeYamlRef.current = ''
|
|
363
|
+
setUpgradeYamlError(null)
|
|
364
|
+
setUpgradeValuesSeeded(false)
|
|
365
|
+
setUpgradePreview(null)
|
|
366
|
+
setUpgradePreviewRequest(null)
|
|
367
|
+
setShowUpgradePreview(false)
|
|
368
|
+
upgradePreviewMutation.reset()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
if (!adjustValues || !upgradeValues || upgradeValuesSeeded) return
|
|
373
|
+
const nextYaml = userSuppliedToYaml(upgradeValues.userSupplied)
|
|
374
|
+
editedUpgradeYamlRef.current = nextYaml
|
|
375
|
+
setEditedUpgradeYaml(nextYaml)
|
|
376
|
+
setUpgradeYamlError(null)
|
|
377
|
+
setUpgradeValuesSeeded(true)
|
|
378
|
+
}, [adjustValues, upgradeValues, upgradeValuesSeeded])
|
|
379
|
+
|
|
380
|
+
const handleToggleAdjustValues = () => {
|
|
381
|
+
if (adjustValues) {
|
|
382
|
+
setAdjustValues(false)
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
setRenderAdjustValues(true)
|
|
387
|
+
setEditedUpgradeYaml('')
|
|
388
|
+
editedUpgradeYamlRef.current = ''
|
|
389
|
+
setUpgradeYamlError(null)
|
|
390
|
+
setUpgradeValuesSeeded(false)
|
|
391
|
+
upgradePreviewMutation.reset()
|
|
392
|
+
setAdjustValues(true)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const getEditedUpgradeValues = () => {
|
|
396
|
+
if (!adjustValues) return {}
|
|
397
|
+
const parsed = parseUpgradeValuesYaml(editedUpgradeYamlRef.current)
|
|
398
|
+
setUpgradeYamlError(parsed.error)
|
|
399
|
+
return parsed.values
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const handleUpgradePreview = async () => {
|
|
403
|
+
if (!targetVersion || upgradeValuesLoading || upgradeValuesError || !upgradeValuesSeeded) return
|
|
404
|
+
const editedValues = getEditedUpgradeValues()
|
|
405
|
+
if (!editedValues) return
|
|
406
|
+
const previewYaml = editedUpgradeYamlRef.current
|
|
407
|
+
const previewRequest: UpgradePreviewRequest = {
|
|
408
|
+
targetVersion,
|
|
409
|
+
values: editedValues,
|
|
410
|
+
repositoryName: upgradeInfo?.repositoryName,
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
const result = await upgradePreviewMutation.mutateAsync({
|
|
414
|
+
namespace: helmNamespace,
|
|
415
|
+
name: release.name,
|
|
416
|
+
values: previewRequest.values,
|
|
417
|
+
version: previewRequest.targetVersion,
|
|
418
|
+
repository: previewRequest.repositoryName,
|
|
419
|
+
})
|
|
420
|
+
if (targetVersionRef.current !== previewRequest.targetVersion || editedUpgradeYamlRef.current !== previewYaml) return
|
|
421
|
+
setUpgradePreview(result)
|
|
422
|
+
setUpgradePreviewRequest(previewRequest)
|
|
423
|
+
setShowUpgradePreview(true)
|
|
424
|
+
} catch {
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const runUpgrade = async (versionToApply: string, editedValuesOverride: Record<string, unknown> | undefined, repositoryName: string | undefined) => {
|
|
430
|
+
if (!versionToApply) return
|
|
431
|
+
|
|
432
|
+
let editedValues = editedValuesOverride
|
|
433
|
+
if (editedValues === undefined && adjustValues) {
|
|
434
|
+
if (upgradeValuesLoading || upgradeValuesError || !upgradeValuesSeeded) return
|
|
435
|
+
editedValues = getEditedUpgradeValues() ?? undefined
|
|
436
|
+
if (!editedValues) return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
setShowUpgradePreview(false)
|
|
262
440
|
setIsUpgrading(true)
|
|
263
441
|
setUpgradeProgress([])
|
|
264
442
|
|
|
@@ -266,8 +444,8 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
266
444
|
await upgradeWithProgress(
|
|
267
445
|
helmNamespace,
|
|
268
446
|
release.name,
|
|
269
|
-
|
|
270
|
-
|
|
447
|
+
versionToApply,
|
|
448
|
+
repositoryName,
|
|
271
449
|
(event) => {
|
|
272
450
|
if (event.type === 'progress' && event.message) {
|
|
273
451
|
setUpgradeProgress(prev => [...prev, {
|
|
@@ -275,24 +453,25 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
275
453
|
message: event.message || '',
|
|
276
454
|
}])
|
|
277
455
|
}
|
|
278
|
-
}
|
|
456
|
+
},
|
|
457
|
+
editedValues
|
|
279
458
|
)
|
|
280
459
|
|
|
281
460
|
setUpgradeProgress(prev => [...prev, {
|
|
282
461
|
phase: 'complete',
|
|
283
|
-
message: `Successfully upgraded to ${
|
|
462
|
+
message: `Successfully upgraded to ${versionToApply}`,
|
|
284
463
|
}])
|
|
285
464
|
|
|
286
465
|
// Invalidate queries
|
|
287
466
|
queryClient.invalidateQueries({ queryKey: ['helm-releases'] })
|
|
288
467
|
queryClient.invalidateQueries({ queryKey: ['helm-release', helmNamespace, release.name] })
|
|
289
468
|
queryClient.invalidateQueries({ queryKey: ['helm-upgrade-info', helmNamespace, release.name] })
|
|
469
|
+
queryClient.invalidateQueries({ queryKey: ['helm-values', helmNamespace, release.name] })
|
|
290
470
|
queryClient.invalidateQueries({ queryKey: ['helm-batch-upgrade-info'] })
|
|
291
471
|
|
|
292
472
|
setTimeout(() => {
|
|
293
473
|
setShowUpgradeConfirm(false)
|
|
294
|
-
|
|
295
|
-
setSelectedVersion(null)
|
|
474
|
+
resetUpgradeDialog()
|
|
296
475
|
refetch()
|
|
297
476
|
switchTab('resources')
|
|
298
477
|
}, 1500)
|
|
@@ -306,6 +485,15 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
306
485
|
}
|
|
307
486
|
}
|
|
308
487
|
|
|
488
|
+
const handleUpgradeConfirm = () => {
|
|
489
|
+
void runUpgrade(targetVersion, undefined, upgradeInfo?.repositoryName)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const handleApplyUpgradePreview = () => {
|
|
493
|
+
if (!upgradePreviewRequest) return
|
|
494
|
+
void runUpgrade(upgradePreviewRequest.targetVersion, upgradePreviewRequest.values, upgradePreviewRequest.repositoryName)
|
|
495
|
+
}
|
|
496
|
+
|
|
309
497
|
const headerHeight = 49
|
|
310
498
|
const dockInset = useDockReservedHeight()
|
|
311
499
|
|
|
@@ -325,7 +513,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
325
513
|
TRANSITION_DRAWER,
|
|
326
514
|
isOpen ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'
|
|
327
515
|
)}
|
|
328
|
-
style={{ width: renderedDrawerWidth, top: headerHeight, height: `calc(100vh - ${headerHeight}px - ${dockInset}px)` }}
|
|
516
|
+
style={{ width: renderedDrawerWidth, top: headerHeight, right: rightInset, height: `calc(100vh - ${headerHeight}px - ${dockInset}px)` }}
|
|
329
517
|
>
|
|
330
518
|
{/* Resize handle */}
|
|
331
519
|
<div
|
|
@@ -401,10 +589,15 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
401
589
|
source via GitOps
|
|
402
590
|
</span>
|
|
403
591
|
</Tooltip>
|
|
404
|
-
) :
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
592
|
+
) : upgradeSourceIssue && !isUpgradeSourceIssueActionable(upgradeSourceIssue) ? (
|
|
593
|
+
<Tooltip content={getUpgradeSourceIssueTooltip(upgradeSourceIssue, upgradeInfo.error, canHelmWrite, helmActReason)}>
|
|
594
|
+
<span className="badge bg-theme-hover/50 text-theme-text-secondary">
|
|
595
|
+
<Link2 className="w-3 h-3" />
|
|
596
|
+
{getUpgradeSourceIssueLabel(upgradeSourceIssue)}
|
|
597
|
+
</span>
|
|
598
|
+
</Tooltip>
|
|
599
|
+
) : isUpgradeSourceIssueActionable(upgradeSourceIssue) ? (
|
|
600
|
+
<Tooltip content={getUpgradeSourceIssueTooltip(upgradeSourceIssue, upgradeInfo.error, canHelmWrite, helmActReason)}>
|
|
408
601
|
<button
|
|
409
602
|
onClick={() => canHelmWrite && setShowTrackSource(true)}
|
|
410
603
|
disabled={!canHelmWrite}
|
|
@@ -414,12 +607,10 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
414
607
|
)}
|
|
415
608
|
>
|
|
416
609
|
<Link2 className="w-3 h-3" />
|
|
417
|
-
|
|
610
|
+
{getUpgradeSourceIssueLabel(upgradeSourceIssue)}
|
|
418
611
|
</button>
|
|
419
612
|
</Tooltip>
|
|
420
613
|
) : (
|
|
421
|
-
// Repo-side error (stale/broken index, classic ambiguity) — not an
|
|
422
|
-
// OCI tracking issue, so surface it without steering to registration.
|
|
423
614
|
<Tooltip content={upgradeInfo.error}>
|
|
424
615
|
<span className="badge bg-theme-hover/50 text-theme-text-secondary">
|
|
425
616
|
upgrade source unresolved
|
|
@@ -626,8 +817,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
626
817
|
open={showUpgradeConfirm}
|
|
627
818
|
onClose={() => {
|
|
628
819
|
setShowUpgradeConfirm(false)
|
|
629
|
-
|
|
630
|
-
setSelectedVersion(null)
|
|
820
|
+
resetUpgradeDialog()
|
|
631
821
|
if (isUpgrading) {
|
|
632
822
|
// Upgrade continues server-side — switch to resources tab to monitor
|
|
633
823
|
setIsUpgrading(false)
|
|
@@ -638,58 +828,201 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
638
828
|
title="Upgrade Release"
|
|
639
829
|
message={`Upgrade "${release.name}" to version ${targetVersion}?`}
|
|
640
830
|
details={upgradeProgress.length === 0
|
|
641
|
-
? `The chart will move from version ${upgradeInfo?.currentVersion} to ${targetVersion}. Your existing values will be preserved. The change is applied to your cluster immediately.`
|
|
831
|
+
? `The chart will move from version ${upgradeInfo?.currentVersion} to ${targetVersion}. ${adjustValues ? 'Your edited values will be applied.' : 'Your existing values will be preserved.'} The change is applied to your cluster immediately.`
|
|
642
832
|
: undefined
|
|
643
833
|
}
|
|
644
834
|
confirmLabel={isDowngrade ? 'Downgrade' : 'Upgrade'}
|
|
645
835
|
variant="warning"
|
|
646
836
|
isLoading={isUpgrading}
|
|
837
|
+
confirmDisabled={isPreviewingUpgrade || (adjustValues && (upgradeValuesLoading || !!upgradeValuesError || !upgradeValuesSeeded || !!upgradeYamlError))}
|
|
647
838
|
isClosable
|
|
839
|
+
className="max-w-2xl"
|
|
648
840
|
>
|
|
649
|
-
{upgradeProgress.length === 0 &&
|
|
841
|
+
{upgradeProgress.length === 0 && (
|
|
650
842
|
<div className="mb-1">
|
|
651
|
-
<label
|
|
843
|
+
<label
|
|
844
|
+
id="upgrade-version-label"
|
|
845
|
+
htmlFor={selectableVersions ? 'upgrade-version' : undefined}
|
|
846
|
+
className="block text-sm font-medium text-theme-text-secondary mb-1.5"
|
|
847
|
+
>
|
|
652
848
|
Target version
|
|
653
849
|
</label>
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
850
|
+
{availableVersionsLoading ? (
|
|
851
|
+
<div
|
|
852
|
+
aria-labelledby="upgrade-version-label"
|
|
853
|
+
aria-live="polite"
|
|
854
|
+
className="flex h-10 items-center gap-2 rounded-lg border border-theme-border-light bg-theme-elevated px-3 text-sm text-theme-text-secondary"
|
|
855
|
+
>
|
|
856
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
857
|
+
Loading available versions...
|
|
858
|
+
</div>
|
|
859
|
+
) : selectableVersions ? (
|
|
860
|
+
<select
|
|
861
|
+
id="upgrade-version"
|
|
862
|
+
value={targetVersion}
|
|
863
|
+
onChange={(e) => {
|
|
864
|
+
targetVersionRef.current = e.target.value
|
|
865
|
+
setSelectedVersion(e.target.value)
|
|
866
|
+
}}
|
|
867
|
+
disabled={isUpgrading || isPreviewingUpgrade}
|
|
868
|
+
className="w-full px-3 py-2 bg-theme-elevated border border-theme-border-light rounded-lg text-sm text-theme-text-primary focus:outline-none focus:ring-2 focus:ring-accent disabled:opacity-50"
|
|
869
|
+
>
|
|
870
|
+
{selectableVersions.map((v) => (
|
|
871
|
+
<option key={v} value={v}>
|
|
872
|
+
{v}
|
|
873
|
+
{v === upgradeInfo?.latestVersion ? ' (latest)' : ''}
|
|
874
|
+
{v === upgradeInfo?.currentVersion ? ' (current)' : ''}
|
|
875
|
+
</option>
|
|
876
|
+
))}
|
|
877
|
+
</select>
|
|
878
|
+
) : (
|
|
879
|
+
<div
|
|
880
|
+
aria-labelledby="upgrade-version-label"
|
|
881
|
+
className="flex h-10 items-center rounded-lg border border-theme-border-light bg-theme-elevated px-3 text-sm text-theme-text-primary"
|
|
882
|
+
>
|
|
883
|
+
{targetVersion}
|
|
884
|
+
</div>
|
|
885
|
+
)}
|
|
669
886
|
{isDowngrade && (
|
|
670
887
|
<p className="mt-1 text-xs text-amber-600 dark:text-amber-400">
|
|
671
888
|
This is a downgrade from {upgradeInfo?.currentVersion}.
|
|
672
889
|
</p>
|
|
673
890
|
)}
|
|
674
|
-
{
|
|
891
|
+
{availableVersionsError && (
|
|
675
892
|
<p className="mt-1 text-xs text-theme-text-tertiary">
|
|
676
|
-
|
|
893
|
+
Version list unavailable; using the detected target version.
|
|
677
894
|
</p>
|
|
678
895
|
)}
|
|
896
|
+
{availableVersions && availableVersions.length >= 50 && (
|
|
897
|
+
<p className="mt-1 text-xs text-theme-text-tertiary">
|
|
898
|
+
Showing the 50 newest versions.
|
|
899
|
+
</p>
|
|
900
|
+
)}
|
|
901
|
+
</div>
|
|
902
|
+
)}
|
|
903
|
+
{upgradeProgress.length === 0 && canHelmWrite && canViewSensitive && (
|
|
904
|
+
<div className="mt-3 border-t border-theme-border pt-3">
|
|
905
|
+
<Tooltip content="Edit the user-supplied Helm values that Radar will pass to this upgrade.">
|
|
906
|
+
<button
|
|
907
|
+
type="button"
|
|
908
|
+
onClick={handleToggleAdjustValues}
|
|
909
|
+
disabled={isUpgrading || isPreviewingUpgrade}
|
|
910
|
+
aria-expanded={adjustValues}
|
|
911
|
+
aria-controls="helm-upgrade-values-panel"
|
|
912
|
+
className="flex items-center gap-1.5 text-sm font-medium text-theme-text-secondary hover:text-theme-text-primary disabled:opacity-50"
|
|
913
|
+
>
|
|
914
|
+
<ChevronRight className={clsx('w-4 h-4 transition-transform duration-200', adjustValues && 'rotate-90')} />
|
|
915
|
+
<SlidersHorizontal className="w-3.5 h-3.5" />
|
|
916
|
+
Adjust your values (optional)
|
|
917
|
+
</button>
|
|
918
|
+
</Tooltip>
|
|
919
|
+
<div
|
|
920
|
+
id="helm-upgrade-values-panel"
|
|
921
|
+
className={`issue-details-motion ${adjustValues ? 'issue-details-motion-open' : ''}`}
|
|
922
|
+
onTransitionEnd={(event) => {
|
|
923
|
+
if (event.target !== event.currentTarget) return
|
|
924
|
+
if (event.propertyName !== 'grid-template-rows') return
|
|
925
|
+
if (!adjustValues) {
|
|
926
|
+
setRenderAdjustValues(false)
|
|
927
|
+
}
|
|
928
|
+
}}
|
|
929
|
+
>
|
|
930
|
+
<div className="overflow-hidden">
|
|
931
|
+
{renderAdjustValues && (
|
|
932
|
+
<div className="mt-2">
|
|
933
|
+
<p className="mb-2 text-xs text-theme-text-tertiary">
|
|
934
|
+
These are your current settings — edit them to carry into {targetVersion}. What you see here is exactly what gets applied.
|
|
935
|
+
</p>
|
|
936
|
+
{upgradeValuesError && (
|
|
937
|
+
<div className="mt-2 px-3 py-2 text-xs text-red-400 bg-red-500/10 border border-red-500/30 rounded">
|
|
938
|
+
Couldn’t load current values: {upgradeValuesError instanceof Error ? upgradeValuesError.message : 'Unknown error'}
|
|
939
|
+
</div>
|
|
940
|
+
)}
|
|
941
|
+
{(upgradeValuesLoading || !upgradeValuesSeeded) && (
|
|
942
|
+
<div className="flex min-h-[180px] items-center gap-2 rounded border border-theme-border bg-theme-elevated px-3 text-sm text-theme-text-secondary">
|
|
943
|
+
<Loader2 className="w-4 h-4 animate-spin" />
|
|
944
|
+
Loading current values...
|
|
945
|
+
</div>
|
|
946
|
+
)}
|
|
947
|
+
{!upgradeValuesLoading && !upgradeValuesError && upgradeValuesSeeded && (
|
|
948
|
+
<textarea
|
|
949
|
+
defaultValue={editedUpgradeYaml}
|
|
950
|
+
onChange={(event) => {
|
|
951
|
+
editedUpgradeYamlRef.current = event.target.value
|
|
952
|
+
if (upgradeYamlError) setUpgradeYamlError(null)
|
|
953
|
+
}}
|
|
954
|
+
onBlur={() => setUpgradeYamlError(parseUpgradeValuesYaml(editedUpgradeYamlRef.current).error)}
|
|
955
|
+
aria-label="Edited Helm values YAML"
|
|
956
|
+
readOnly={isPreviewingUpgrade || isUpgrading}
|
|
957
|
+
spellCheck={false}
|
|
958
|
+
autoCorrect="off"
|
|
959
|
+
autoCapitalize="off"
|
|
960
|
+
rows={9}
|
|
961
|
+
className="max-h-[360px] min-h-[180px] w-full resize-y rounded-lg border border-theme-border bg-theme-elevated px-3 py-2 font-mono text-xs leading-5 text-theme-text-primary placeholder:text-theme-text-tertiary focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30 read-only:opacity-70"
|
|
962
|
+
/>
|
|
963
|
+
)}
|
|
964
|
+
{upgradeYamlError && (
|
|
965
|
+
<div className="mt-2 px-3 py-2 text-xs text-red-400 bg-red-500/10 border border-red-500/30 rounded">
|
|
966
|
+
{upgradeYamlError}
|
|
967
|
+
</div>
|
|
968
|
+
)}
|
|
969
|
+
{upgradePreviewMutation.error && (
|
|
970
|
+
<div className="mt-2 px-3 py-2 text-xs text-red-400 bg-red-500/10 border border-red-500/30 rounded">
|
|
971
|
+
Couldn’t preview against {targetVersion}: {upgradePreviewMutation.error.message}. You can still upgrade.
|
|
972
|
+
</div>
|
|
973
|
+
)}
|
|
974
|
+
<div className="mt-2 flex justify-end">
|
|
975
|
+
<Tooltip
|
|
976
|
+
content="Render this release with the selected chart version and edited values, then show the manifest diff. Nothing is applied until you confirm from the preview."
|
|
977
|
+
wrapperClassName="shrink-0"
|
|
978
|
+
>
|
|
979
|
+
<button
|
|
980
|
+
type="button"
|
|
981
|
+
onClick={handleUpgradePreview}
|
|
982
|
+
disabled={upgradeValuesLoading || !!upgradeValuesError || !upgradeValuesSeeded || !!upgradeYamlError || isPreviewingUpgrade || isUpgrading}
|
|
983
|
+
className="flex items-center gap-1 px-2.5 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded border border-theme-border disabled:opacity-50 disabled:cursor-not-allowed"
|
|
984
|
+
>
|
|
985
|
+
{upgradePreviewMutation.isPending ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
|
|
986
|
+
Preview what changes
|
|
987
|
+
</button>
|
|
988
|
+
</Tooltip>
|
|
989
|
+
</div>
|
|
990
|
+
</div>
|
|
991
|
+
)}
|
|
992
|
+
</div>
|
|
993
|
+
</div>
|
|
679
994
|
</div>
|
|
680
995
|
)}
|
|
681
996
|
{upgradeProgress.length > 0 && <ProgressLog entries={upgradeProgress} />}
|
|
682
997
|
</ConfirmDialog>
|
|
683
998
|
|
|
999
|
+
{showUpgradePreview && upgradePreview && upgradePreviewRequest && (
|
|
1000
|
+
<ValuesDiffPreview
|
|
1001
|
+
previewData={upgradePreview}
|
|
1002
|
+
onClose={() => setShowUpgradePreview(false)}
|
|
1003
|
+
onApply={handleApplyUpgradePreview}
|
|
1004
|
+
isApplying={isUpgrading}
|
|
1005
|
+
title={`Preview upgrade to ${upgradePreviewRequest.targetVersion}`}
|
|
1006
|
+
applyLabel={upgradeInfo?.currentVersion && compareVersions(upgradePreviewRequest.targetVersion, upgradeInfo.currentVersion) === -1 ? 'Downgrade' : 'Upgrade'}
|
|
1007
|
+
/>
|
|
1008
|
+
)}
|
|
1009
|
+
|
|
684
1010
|
<TrackChartSourceDialog
|
|
685
1011
|
open={showTrackSource}
|
|
686
1012
|
onClose={() => setShowTrackSource(false)}
|
|
687
1013
|
chartName={releaseDetail?.chart}
|
|
1014
|
+
sourceIssue={upgradeSourceIssue}
|
|
1015
|
+
sourceError={upgradeInfo?.error}
|
|
688
1016
|
/>
|
|
689
1017
|
</div>
|
|
690
1018
|
)
|
|
691
1019
|
}
|
|
692
1020
|
|
|
1021
|
+
function userSuppliedToYaml(userSupplied: Record<string, unknown> | undefined): string {
|
|
1022
|
+
if (!userSupplied || Object.keys(userSupplied).length === 0) return ''
|
|
1023
|
+
return yaml.stringify(userSupplied, { lineWidth: 0 })
|
|
1024
|
+
}
|
|
1025
|
+
|
|
693
1026
|
// Shared progress log for streaming Helm operations
|
|
694
1027
|
function ProgressLog({ entries }: { entries: { phase: string; message: string }[] }) {
|
|
695
1028
|
return (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useMemo, useRef, useEffect, useCallback, forwardRef } from 'react'
|
|
2
2
|
import { useRegisterShortcuts } from '../../hooks/useKeyboardShortcuts'
|
|
3
3
|
import { Package, Search, ArrowUpCircle, LayoutGrid, List, Shield, GitBranch, ChevronRight, RotateCcw, Clock } from 'lucide-react'
|
|
4
|
-
import { PaneLoader, PageHeader, SortableTh, FreshnessControl, type SortDir } from '@skyhook-io/k8s-ui'
|
|
4
|
+
import { PaneLoader, PageHeader, SortableTh, FreshnessControl, type SortDir, Input } from '@skyhook-io/k8s-ui'
|
|
5
5
|
import { useConnection } from '../../context/ConnectionContext'
|
|
6
6
|
import { clsx } from 'clsx'
|
|
7
7
|
import { useHelmReleases, useHelmBatchUpgradeInfo, isForbiddenError } from '../../api/client'
|
|
@@ -240,9 +240,8 @@ export function HelmView({ namespaces, selectedRelease, onReleaseClick }: HelmVi
|
|
|
240
240
|
<div className="flex items-center gap-4 px-4 py-3 border-b border-theme-border bg-theme-surface/50 shrink-0">
|
|
241
241
|
<div className="flex-1 relative">
|
|
242
242
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-theme-text-tertiary" />
|
|
243
|
-
<
|
|
243
|
+
<Input
|
|
244
244
|
ref={searchInputRef}
|
|
245
|
-
type="text"
|
|
246
245
|
placeholder="Search releases..."
|
|
247
246
|
value={searchTerm}
|
|
248
247
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useMemo, useRef } from 'react'
|
|
2
2
|
import { X, Package, ChevronRight, ChevronLeft, Play, Loader2, AlertTriangle, CheckCircle, User, BookOpen, Link as LinkIcon, Star, BadgeCheck, Shield, Globe, Building2, Plus, Minus, Terminal } from 'lucide-react'
|
|
3
|
-
import { PaneLoader } from '@skyhook-io/k8s-ui'
|
|
3
|
+
import { PaneLoader, Input } from '@skyhook-io/k8s-ui'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
5
5
|
import yaml from 'yaml'
|
|
6
6
|
import { createPatch } from 'diff'
|
|
@@ -561,8 +561,7 @@ function InfoStep({
|
|
|
561
561
|
<label className="block text-sm font-medium text-theme-text-secondary mb-2">
|
|
562
562
|
Release Name
|
|
563
563
|
</label>
|
|
564
|
-
<
|
|
565
|
-
type="text"
|
|
564
|
+
<Input
|
|
566
565
|
value={releaseName}
|
|
567
566
|
onChange={(e) => setReleaseName(e.target.value)}
|
|
568
567
|
placeholder="my-release"
|
|
@@ -591,8 +590,7 @@ function InfoStep({
|
|
|
591
590
|
<label className="block text-sm font-medium text-theme-text-secondary mb-2">
|
|
592
591
|
Namespace
|
|
593
592
|
</label>
|
|
594
|
-
<
|
|
595
|
-
type="text"
|
|
593
|
+
<Input
|
|
596
594
|
list="namespace-suggestions"
|
|
597
595
|
value={namespace}
|
|
598
596
|
onChange={(e) => setNamespace(e.target.value)}
|