@skyhook-io/radar-app 1.16.2 → 1.17.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 +5 -5
- package/src/App.tsx +60 -12
- package/src/RadarApp.tsx +27 -17
- package/src/api/client.goneResource.test.ts +49 -0
- package/src/api/client.ts +168 -55
- package/src/api/cnpg.ts +95 -0
- package/src/api/diagnose.test.ts +40 -0
- package/src/api/diagnose.ts +12 -1
- package/src/api/radarFeatures.render.test.tsx +19 -1
- package/src/api/radarFeatures.ts +3 -0
- package/src/api/timelineSource.ts +1 -1
- package/src/api/traffic.sources.test.ts +55 -0
- package/src/api/traffic.ts +173 -14
- package/src/components/NoClusterAccessBanner.tsx +68 -0
- package/src/components/applications/ApplicationsView.tsx +3 -0
- package/src/components/capacity/CapacityPoolDetail.tsx +18 -7
- package/src/components/capacity/CapacityView.test.tsx +31 -1
- package/src/components/capacity/shared.tsx +12 -21
- package/src/components/cnpg/CNPGClusterActivity.tsx +49 -0
- package/src/components/cnpg/CNPGClusterLogs.tsx +62 -0
- package/src/components/cnpg/CNPGDeclarations.tsx +311 -0
- package/src/components/cnpg/CNPGDetailPage.tsx +228 -0
- package/src/components/cnpg/CNPGDrawerTrail.tsx +37 -0
- package/src/components/cnpg/CNPGOperator.tsx +234 -0
- package/src/components/cnpg/CNPGOverview.tsx +307 -0
- package/src/components/cnpg/CNPGPooling.tsx +116 -0
- package/src/components/cnpg/CNPGProtection.tsx +320 -0
- package/src/components/cnpg/CNPGSummaryHost.tsx +143 -0
- package/src/components/cnpg/CNPGView.tsx +156 -0
- package/src/components/cnpg/paths.ts +13 -0
- package/src/components/cnpg/routes.test.ts +60 -0
- package/src/components/cnpg/routes.ts +112 -0
- package/src/components/cnpg/shared.tsx +290 -0
- package/src/components/cnpg/useCNPGSidebarWorkspace.ts +88 -0
- package/src/components/diagnose/AgentSetupNotice.test.tsx +58 -0
- package/src/components/diagnose/AgentSetupNotice.tsx +142 -51
- package/src/components/diagnose/ApplyDialog.ack.test.tsx +47 -0
- package/src/components/diagnose/ApplyDialog.test.tsx +59 -6
- package/src/components/diagnose/ApplyDialog.tsx +26 -28
- package/src/components/diagnose/DiagnoseContext.test.ts +43 -0
- package/src/components/diagnose/DiagnoseContext.tsx +136 -54
- package/src/components/diagnose/DiagnoseSurface.tsx +16 -3
- package/src/components/diagnose/InvestigationView.tsx +39 -2
- package/src/components/diagnose/LocalDiagnoseAction.tsx +40 -20
- package/src/components/diagnose/agentCatalog.ts +2 -2
- package/src/components/diagnose/diagnoseEvidenceTypes.ts +2 -0
- package/src/components/dock/TrafficFlowListTab.tsx +27 -6
- package/src/components/execution/BatchExecutionView.tsx +11 -11
- package/src/components/helm/ChartBrowser.tsx +9 -10
- package/src/components/helm/HelmCompareRoute.tsx +108 -115
- package/src/components/helm/HelmReleaseDrawer.tsx +26 -44
- package/src/components/helm/InstallWizard.tsx +5 -2
- package/src/components/helm/RevisionHistory.tsx +3 -2
- package/src/components/helm/TrackChartSourceDialog.tsx +10 -2
- package/src/components/helm/ValuesViewer.tsx +1 -1
- package/src/components/home/ClusterHealthCard.tsx +7 -0
- package/src/components/home/mcpToolCatalog.ts +1 -1
- package/src/components/resources/ResourceDetailDrawer.tsx +6 -0
- package/src/components/resources/ResourcesView.tsx +6 -38
- package/src/components/resources/renderers/KyvernoPolicyQueued.tsx +2 -1
- package/src/components/resources/renderers/NodeRenderer.tsx +7 -1
- package/src/components/resources/renderers/ServiceRenderer.test.tsx +81 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +19 -14
- package/src/components/settings/SettingsDialog.tsx +122 -19
- package/src/components/shared/LargeClusterNamespacePicker.tsx +11 -5
- package/src/components/timeline/TimelineList.test.ts +47 -29
- package/src/components/timeline/TimelineList.tsx +60 -11
- package/src/components/timeline/TimelineRetentionNote.test.tsx +49 -0
- package/src/components/timeline/TimelineRetentionNote.tsx +20 -0
- package/src/components/timeline/TimelineView.tsx +16 -2
- package/src/components/timeline/TimelineView.urlparams.test.ts +6 -0
- package/src/components/traffic/TrafficConnectionsTable.tsx +144 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +22 -1
- package/src/components/traffic/TrafficFlowListContext.tsx +12 -4
- package/src/components/traffic/TrafficFocusSearch.tsx +172 -0
- package/src/components/traffic/TrafficGraph.tsx +109 -26
- package/src/components/traffic/TrafficGraphTooLarge.tsx +150 -0
- package/src/components/traffic/TrafficView.tsx +582 -149
- package/src/components/traffic/trafficFilters.test.ts +398 -1
- package/src/components/traffic/trafficFilters.ts +408 -0
- package/src/components/whats-new/WhatsNew.test.ts +107 -28
- package/src/components/whats-new/WhatsNew.tsx +146 -86
- package/src/components/whats-new/WhatsNewDialog.test.tsx +132 -10
- package/src/components/whats-new/releaseNotes.ts +155 -24
- package/src/components/workload/WorkloadView.test.ts +2 -1
- package/src/components/workload/WorkloadView.tsx +165 -294
- package/src/context/TimelineCustomization.tsx +15 -0
- package/src/contexts/CapabilitiesContext.tsx +9 -1
- package/src/hooks/useGitOpsWriteGuard.test.tsx +457 -0
- package/src/hooks/useGitOpsWriteGuard.ts +193 -0
- package/src/hooks/useResolvedGitOpsOwner.ts +256 -0
- package/src/hooks/useResourceCounts.ts +49 -0
- package/src/components/helm/RoleGatedPanel.tsx +0 -47
|
@@ -3,12 +3,12 @@ import { Search, RefreshCw, Package, Database, AlertCircle, ExternalLink, Chevro
|
|
|
3
3
|
import { PaneLoader, Input } from '@skyhook-io/k8s-ui'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
5
5
|
import { useHelmRepositories, useSearchCharts, useUpdateRepository, useUpdateRepositorySilent, useArtifactHubSearch, type ArtifactHubSortOption } from '../../api/client'
|
|
6
|
-
import { useCanHelmWrite } from '../../contexts/CapabilitiesContext'
|
|
6
|
+
import { useCanHelmWrite, useIsAuthEnabled } from '../../contexts/CapabilitiesContext'
|
|
7
7
|
import type { ChartInfo, HelmRepository, ArtifactHubChart, ChartSource } from '../../types'
|
|
8
|
-
import { formatAge } from './helm-utils'
|
|
9
8
|
import { SEVERITY_BADGE } from '../../utils/badge-colors'
|
|
10
9
|
import { Tooltip } from '../ui/Tooltip'
|
|
11
10
|
import { useToast } from '../ui/Toast'
|
|
11
|
+
import { formatRelativeAgeTime } from '@skyhook-io/k8s-ui/utils/format'
|
|
12
12
|
|
|
13
13
|
interface ChartBrowserProps {
|
|
14
14
|
onChartSelect: (repo: string, chart: string, version: string, source: ChartSource) => void
|
|
@@ -25,12 +25,11 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
|
|
|
25
25
|
const [showVerifiedOnly, setShowVerifiedOnly] = useState(false)
|
|
26
26
|
const [artifactHubSort, setArtifactHubSort] = useState<ArtifactHubSortOption>('relevance')
|
|
27
27
|
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const canHelmWrite = useCanHelmWrite()
|
|
28
|
+
// Refreshing repositories only touches Radar's own chart cache. With auth on,
|
|
29
|
+
// any signed-in user may do it; without auth it follows rbac.helm.
|
|
30
|
+
const helmWrite = useCanHelmWrite()
|
|
31
|
+
const authEnabled = useIsAuthEnabled()
|
|
32
|
+
const canHelmWrite = helmWrite || authEnabled
|
|
34
33
|
const helmWriteReason = canHelmWrite ? '' : 'Helm write permissions required. Set rbac.helm=true in the Radar Helm chart values.'
|
|
35
34
|
|
|
36
35
|
// Local repo hooks
|
|
@@ -432,7 +431,7 @@ function RepoDropdownItem({ repo, isSelected, onSelect, onUpdate, isUpdating, ca
|
|
|
432
431
|
{repo.name}
|
|
433
432
|
{repo.lastUpdated && (
|
|
434
433
|
<span className="text-xs text-theme-text-tertiary ml-2">
|
|
435
|
-
{
|
|
434
|
+
{formatRelativeAgeTime(repo.lastUpdated)}
|
|
436
435
|
</span>
|
|
437
436
|
)}
|
|
438
437
|
</button>
|
|
@@ -515,7 +514,7 @@ interface ArtifactHubChartCardProps {
|
|
|
515
514
|
function ArtifactHubChartCard({ chart, onSelect }: ArtifactHubChartCardProps) {
|
|
516
515
|
// Format Unix timestamp to relative age
|
|
517
516
|
const lastUpdated = chart.updatedAt
|
|
518
|
-
?
|
|
517
|
+
? formatRelativeAgeTime(new Date(chart.updatedAt * 1000).toISOString())
|
|
519
518
|
: null
|
|
520
519
|
|
|
521
520
|
return (
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
} from 'lucide-react'
|
|
20
20
|
import { PaneLoader, DiffLine, hasDiffBodyChange } from '@skyhook-io/k8s-ui'
|
|
21
21
|
import {
|
|
22
|
-
useCloudRole,
|
|
23
22
|
useHelmHooksDiff,
|
|
24
23
|
useHelmManifestDiff,
|
|
25
24
|
useHelmNotesDiff,
|
|
@@ -30,7 +29,6 @@ import {
|
|
|
30
29
|
import type { HelmHook, HelmRevision, HooksDiff, ResourceDiff } from '../../types'
|
|
31
30
|
import { getHelmStatusColor, getKindBadgeColor, SEVERITY_BADGE } from '../../utils/badge-colors'
|
|
32
31
|
import { formatDate } from './helm-utils'
|
|
33
|
-
import { RoleGatedPanel } from './RoleGatedPanel'
|
|
34
32
|
import { Tooltip } from '../ui/Tooltip'
|
|
35
33
|
import { TRANSITION_MENU, overlayExitMs, overlayTransitionStyle } from '../../utils/animation'
|
|
36
34
|
import { useAnimatedUnmount } from '../../hooks/useAnimatedUnmount'
|
|
@@ -53,8 +51,6 @@ export function HelmCompareRoute() {
|
|
|
53
51
|
const navigate = useNavigate()
|
|
54
52
|
const location = useLocation()
|
|
55
53
|
const [searchParams, setSearchParams] = useSearchParams()
|
|
56
|
-
const { canAtLeast } = useCloudRole()
|
|
57
|
-
const canViewSensitive = canAtLeast('member')
|
|
58
54
|
|
|
59
55
|
const releaseRef = parseReleaseParam(searchParams.get('release'))
|
|
60
56
|
const storageNamespace = searchParams.get('releaseStorage') || undefined
|
|
@@ -74,16 +70,15 @@ export function HelmCompareRoute() {
|
|
|
74
70
|
const revision1 = defaultLeftRevision
|
|
75
71
|
const revision2 = defaultRightRevision
|
|
76
72
|
const pairReady = Boolean(helmNamespace && releaseName && revision1 > 0 && revision2 > 0 && revision1 !== revision2)
|
|
77
|
-
const diffEnabled = canViewSensitive && pairReady
|
|
78
73
|
|
|
79
74
|
const left = revisions.find((r) => r.revision === revision1)
|
|
80
75
|
const right = revisions.find((r) => r.revision === revision2)
|
|
81
76
|
|
|
82
|
-
const manifestDiff = useHelmManifestDiff(helmNamespace, releaseName, revision1, revision2,
|
|
83
|
-
const valuesDiff = useHelmValuesDiff(helmNamespace, releaseName, revision1, revision2, false,
|
|
84
|
-
const notesDiff = useHelmNotesDiff(helmNamespace, releaseName, revision1, revision2,
|
|
85
|
-
const hooksDiff = useHelmHooksDiff(helmNamespace, releaseName, revision1, revision2,
|
|
86
|
-
const resourceDiff = useHelmResourceDiff(helmNamespace, releaseName, revision1, revision2,
|
|
77
|
+
const manifestDiff = useHelmManifestDiff(helmNamespace, releaseName, revision1, revision2, pairReady)
|
|
78
|
+
const valuesDiff = useHelmValuesDiff(helmNamespace, releaseName, revision1, revision2, false, pairReady)
|
|
79
|
+
const notesDiff = useHelmNotesDiff(helmNamespace, releaseName, revision1, revision2, pairReady)
|
|
80
|
+
const hooksDiff = useHelmHooksDiff(helmNamespace, releaseName, revision1, revision2, pairReady)
|
|
81
|
+
const resourceDiff = useHelmResourceDiff(helmNamespace, releaseName, revision1, revision2, pairReady)
|
|
87
82
|
|
|
88
83
|
const updateRevision = useCallback(
|
|
89
84
|
(key: 'revision1' | 'revision2', value: number) => {
|
|
@@ -214,120 +209,118 @@ export function HelmCompareRoute() {
|
|
|
214
209
|
</div>
|
|
215
210
|
</header>
|
|
216
211
|
|
|
217
|
-
<
|
|
218
|
-
<div className=
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
<div className="
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
)}
|
|
212
|
+
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
213
|
+
<div className={clsx('grid w-full grid-cols-1 gap-4 px-4 py-4', pairReady && 'xl:grid-cols-[220px_minmax(0,1fr)]')}>
|
|
214
|
+
{pairReady && (
|
|
215
|
+
<nav className="hidden xl:block">
|
|
216
|
+
<div className="sticky top-4 rounded-xl border border-theme-border bg-theme-surface p-2 shadow-theme-sm">
|
|
217
|
+
<div className="px-2 pb-2 text-[11px] font-medium uppercase text-theme-text-tertiary">Compare</div>
|
|
218
|
+
{[
|
|
219
|
+
['summary', 'Summary'],
|
|
220
|
+
['manifest', 'Manifest'],
|
|
221
|
+
['resources', 'Resources'],
|
|
222
|
+
['values', 'Values'],
|
|
223
|
+
['hooks', 'Hooks'],
|
|
224
|
+
['notes', 'Notes'],
|
|
225
|
+
].map(([id, label]) => (
|
|
226
|
+
<a
|
|
227
|
+
key={id}
|
|
228
|
+
href={`#${id}`}
|
|
229
|
+
onClick={(event) => scrollCompareSection(event, id)}
|
|
230
|
+
className="block rounded-md px-2 py-1.5 text-xs text-theme-text-secondary hover:bg-theme-elevated hover:text-theme-text-primary"
|
|
231
|
+
>
|
|
232
|
+
{label}
|
|
233
|
+
</a>
|
|
234
|
+
))}
|
|
235
|
+
</div>
|
|
236
|
+
</nav>
|
|
237
|
+
)}
|
|
244
238
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
</div>
|
|
239
|
+
<main className="min-w-0 space-y-4">
|
|
240
|
+
{!pairReady ? (
|
|
241
|
+
<div className="card-inner-lg flex items-start gap-3">
|
|
242
|
+
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-amber-500" />
|
|
243
|
+
<div>
|
|
244
|
+
<div className="text-sm font-medium text-theme-text-primary">Pick two different revisions to compare</div>
|
|
245
|
+
<div className="mt-1 text-sm text-theme-text-secondary">Use the revision selectors above to choose a source and target revision.</div>
|
|
253
246
|
</div>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
right={right}
|
|
260
|
-
revision1={revision1}
|
|
261
|
-
revision2={revision2}
|
|
262
|
-
manifestDiff={manifestDiff.data?.diff}
|
|
263
|
-
manifestLoading={manifestDiff.isLoading}
|
|
264
|
-
manifestError={manifestDiff.error}
|
|
265
|
-
valuesDiff={valuesDiff.data?.diff}
|
|
266
|
-
valuesLoading={valuesDiff.isLoading}
|
|
267
|
-
valuesError={valuesDiff.error}
|
|
268
|
-
notesDiff={notesDiff.data?.diff}
|
|
269
|
-
notesLoading={notesDiff.isLoading}
|
|
270
|
-
notesError={notesDiff.error}
|
|
271
|
-
hooksDiff={hooksDiff.data}
|
|
272
|
-
hooksLoading={hooksDiff.isLoading}
|
|
273
|
-
hooksError={hooksDiff.error}
|
|
274
|
-
resourceDiff={resourceDiff.data}
|
|
275
|
-
resourceLoading={resourceDiff.isLoading}
|
|
276
|
-
resourceError={resourceDiff.error}
|
|
277
|
-
/>
|
|
278
|
-
</section>
|
|
279
|
-
|
|
280
|
-
<DiffSection
|
|
281
|
-
id="manifest"
|
|
282
|
-
icon={Code}
|
|
283
|
-
title="Rendered manifest diff"
|
|
284
|
-
description="Rendered Kubernetes YAML is the ground truth for what Helm would apply between these revisions."
|
|
285
|
-
diff={manifestDiff.data?.diff || ''}
|
|
286
|
-
isLoading={manifestDiff.isLoading}
|
|
287
|
-
error={manifestDiff.error}
|
|
288
|
-
emptyLabel="No rendered manifest changes found."
|
|
289
|
-
/>
|
|
290
|
-
|
|
291
|
-
<ResourceInventoryDiffSection
|
|
292
|
-
diff={resourceDiff.data}
|
|
293
|
-
isLoading={resourceDiff.isLoading}
|
|
294
|
-
error={resourceDiff.error}
|
|
247
|
+
</div>
|
|
248
|
+
) : (
|
|
249
|
+
<>
|
|
250
|
+
<section id="summary" className="scroll-mt-4 space-y-4">
|
|
251
|
+
<CompareSummary
|
|
295
252
|
left={left}
|
|
296
253
|
right={right}
|
|
297
254
|
revision1={revision1}
|
|
298
255
|
revision2={revision2}
|
|
256
|
+
manifestDiff={manifestDiff.data?.diff}
|
|
257
|
+
manifestLoading={manifestDiff.isLoading}
|
|
258
|
+
manifestError={manifestDiff.error}
|
|
259
|
+
valuesDiff={valuesDiff.data?.diff}
|
|
260
|
+
valuesLoading={valuesDiff.isLoading}
|
|
261
|
+
valuesError={valuesDiff.error}
|
|
262
|
+
notesDiff={notesDiff.data?.diff}
|
|
263
|
+
notesLoading={notesDiff.isLoading}
|
|
264
|
+
notesError={notesDiff.error}
|
|
265
|
+
hooksDiff={hooksDiff.data}
|
|
266
|
+
hooksLoading={hooksDiff.isLoading}
|
|
267
|
+
hooksError={hooksDiff.error}
|
|
268
|
+
resourceDiff={resourceDiff.data}
|
|
269
|
+
resourceLoading={resourceDiff.isLoading}
|
|
270
|
+
resourceError={resourceDiff.error}
|
|
299
271
|
/>
|
|
272
|
+
</section>
|
|
273
|
+
|
|
274
|
+
<DiffSection
|
|
275
|
+
id="manifest"
|
|
276
|
+
icon={Code}
|
|
277
|
+
title="Rendered manifest diff"
|
|
278
|
+
description="Rendered Kubernetes YAML is the ground truth for what Helm would apply between these revisions."
|
|
279
|
+
diff={manifestDiff.data?.diff || ''}
|
|
280
|
+
isLoading={manifestDiff.isLoading}
|
|
281
|
+
error={manifestDiff.error}
|
|
282
|
+
emptyLabel="No rendered manifest changes found."
|
|
283
|
+
/>
|
|
284
|
+
|
|
285
|
+
<ResourceInventoryDiffSection
|
|
286
|
+
diff={resourceDiff.data}
|
|
287
|
+
isLoading={resourceDiff.isLoading}
|
|
288
|
+
error={resourceDiff.error}
|
|
289
|
+
left={left}
|
|
290
|
+
right={right}
|
|
291
|
+
revision1={revision1}
|
|
292
|
+
revision2={revision2}
|
|
293
|
+
/>
|
|
294
|
+
|
|
295
|
+
<DiffSection
|
|
296
|
+
id="values"
|
|
297
|
+
icon={Settings}
|
|
298
|
+
title="User-supplied values diff"
|
|
299
|
+
description="Only values explicitly supplied to the release are compared here; computed chart defaults can still affect the rendered manifest."
|
|
300
|
+
diff={valuesDiff.data?.diff || ''}
|
|
301
|
+
isLoading={valuesDiff.isLoading}
|
|
302
|
+
error={valuesDiff.error}
|
|
303
|
+
emptyLabel="No user-supplied value changes found."
|
|
304
|
+
/>
|
|
305
|
+
|
|
306
|
+
<HooksDiffSection diff={hooksDiff.data} isLoading={hooksDiff.isLoading} error={hooksDiff.error} />
|
|
307
|
+
|
|
308
|
+
<DiffSection
|
|
309
|
+
id="notes"
|
|
310
|
+
icon={FileText}
|
|
311
|
+
title="Release notes diff"
|
|
312
|
+
description="NOTES.txt output can reveal chart-level instructions that changed without changing live Kubernetes objects."
|
|
313
|
+
diff={notesDiff.data?.diff || ''}
|
|
314
|
+
isLoading={notesDiff.isLoading}
|
|
315
|
+
error={notesDiff.error}
|
|
316
|
+
emptyLabel="No release notes changes found."
|
|
317
|
+
/>
|
|
300
318
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
title="User-supplied values diff"
|
|
305
|
-
description="Only values explicitly supplied to the release are compared here; computed chart defaults can still affect the rendered manifest."
|
|
306
|
-
diff={valuesDiff.data?.diff || ''}
|
|
307
|
-
isLoading={valuesDiff.isLoading}
|
|
308
|
-
error={valuesDiff.error}
|
|
309
|
-
emptyLabel="No user-supplied value changes found."
|
|
310
|
-
/>
|
|
311
|
-
|
|
312
|
-
<HooksDiffSection diff={hooksDiff.data} isLoading={hooksDiff.isLoading} error={hooksDiff.error} />
|
|
313
|
-
|
|
314
|
-
<DiffSection
|
|
315
|
-
id="notes"
|
|
316
|
-
icon={FileText}
|
|
317
|
-
title="Release notes diff"
|
|
318
|
-
description="NOTES.txt output can reveal chart-level instructions that changed without changing live Kubernetes objects."
|
|
319
|
-
diff={notesDiff.data?.diff || ''}
|
|
320
|
-
isLoading={notesDiff.isLoading}
|
|
321
|
-
error={notesDiff.error}
|
|
322
|
-
emptyLabel="No release notes changes found."
|
|
323
|
-
/>
|
|
324
|
-
|
|
325
|
-
</>
|
|
326
|
-
)}
|
|
327
|
-
</main>
|
|
328
|
-
</div>
|
|
319
|
+
</>
|
|
320
|
+
)}
|
|
321
|
+
</main>
|
|
329
322
|
</div>
|
|
330
|
-
</
|
|
323
|
+
</div>
|
|
331
324
|
</div>
|
|
332
325
|
)
|
|
333
326
|
}
|
|
@@ -18,8 +18,7 @@ import type { SelectedHelmRelease, HelmHook, ChartDependency, HelmOperation, Hel
|
|
|
18
18
|
import { apiVersionToGroup, kindToPluralWithGroup, type NavigateToResource } from '../../utils/navigation'
|
|
19
19
|
import { formatDate } from './helm-utils'
|
|
20
20
|
import { getHelmStatusColor, getKindBadgeColor, getResourceStatusColor, SEVERITY_BADGE, SEVERITY_TEXT } from '../../utils/badge-colors'
|
|
21
|
-
import { useCanHelmAct
|
|
22
|
-
import { RoleGatedPanel } from './RoleGatedPanel'
|
|
21
|
+
import { useCanHelmAct } from '../../api/client'
|
|
23
22
|
import { RevisionHistory } from './RevisionHistory'
|
|
24
23
|
import { ManifestViewer } from './ManifestViewer'
|
|
25
24
|
import { ValuesViewer } from './ValuesViewer'
|
|
@@ -129,14 +128,8 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
129
128
|
const resizeStartWidth = useRef(DEFAULT_WIDTH)
|
|
130
129
|
const targetVersionRef = useRef('')
|
|
131
130
|
const editedUpgradeYamlRef = useRef('')
|
|
132
|
-
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct()
|
|
133
|
-
// Cloud viewers can't view release manifests / values / diffs
|
|
134
|
-
// (backend gate at requireCloudRole('member')). Skip the queries
|
|
135
|
-
// when the role would 403 — saves a round-trip and avoids a
|
|
136
|
-
// transient error state under the role-gated panel.
|
|
137
|
-
const { canAtLeast } = useCloudRole()
|
|
138
|
-
const canViewSensitive = canAtLeast('member')
|
|
139
131
|
const helmNamespace = release.storageNamespace || release.namespace
|
|
132
|
+
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct(helmNamespace)
|
|
140
133
|
|
|
141
134
|
const { data: releaseDetail, isLoading, error: releaseError, refetch: refetchRelease } = useHelmRelease(
|
|
142
135
|
helmNamespace,
|
|
@@ -149,7 +142,6 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
149
142
|
helmNamespace,
|
|
150
143
|
release.name,
|
|
151
144
|
selectedRevision,
|
|
152
|
-
canViewSensitive,
|
|
153
145
|
)
|
|
154
146
|
|
|
155
147
|
// Fetch values
|
|
@@ -157,7 +149,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
157
149
|
helmNamespace,
|
|
158
150
|
release.name,
|
|
159
151
|
showAllValues,
|
|
160
|
-
|
|
152
|
+
true,
|
|
161
153
|
selectedRevision,
|
|
162
154
|
)
|
|
163
155
|
const {
|
|
@@ -168,7 +160,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
168
160
|
helmNamespace,
|
|
169
161
|
release.name,
|
|
170
162
|
false,
|
|
171
|
-
|
|
163
|
+
showUpgradeConfirm && adjustValues,
|
|
172
164
|
)
|
|
173
165
|
|
|
174
166
|
// Lazy check for upgrade availability
|
|
@@ -714,7 +706,6 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
714
706
|
releaseNamespace={releaseDetail.namespace}
|
|
715
707
|
managedByFluxHelmRelease={releaseDetail.managedByFluxHelmRelease}
|
|
716
708
|
hookDiagnostics={releaseDetail.hookDiagnostics}
|
|
717
|
-
canCompare={canViewSensitive}
|
|
718
709
|
onCompare={handleCompareRevisions}
|
|
719
710
|
onNavigateToResource={onNavigateToResource}
|
|
720
711
|
/>
|
|
@@ -732,32 +723,28 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
732
723
|
/>
|
|
733
724
|
)}
|
|
734
725
|
{activeTab === 'manifest' && (
|
|
735
|
-
<
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
/>
|
|
743
|
-
</RoleGatedPanel>
|
|
726
|
+
<ManifestViewer
|
|
727
|
+
manifest={manifest || ''}
|
|
728
|
+
isLoading={manifestLoading}
|
|
729
|
+
revision={selectedRevision}
|
|
730
|
+
onCopy={(text) => copyToClipboard(text, 'manifest')}
|
|
731
|
+
copied={copied === 'manifest'}
|
|
732
|
+
/>
|
|
744
733
|
)}
|
|
745
734
|
{activeTab === 'values' && (
|
|
746
|
-
<
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
/>
|
|
760
|
-
</RoleGatedPanel>
|
|
735
|
+
<ValuesViewer
|
|
736
|
+
values={values}
|
|
737
|
+
isLoading={valuesLoading}
|
|
738
|
+
showAllValues={showAllValues}
|
|
739
|
+
onToggleAllValues={setShowAllValues}
|
|
740
|
+
onCopy={(text) => copyToClipboard(text, 'values')}
|
|
741
|
+
copied={copied === 'values'}
|
|
742
|
+
namespace={helmNamespace}
|
|
743
|
+
name={release.name}
|
|
744
|
+
revision={selectedRevision}
|
|
745
|
+
currentRevision={releaseDetail.revision}
|
|
746
|
+
onApplySuccess={() => refetch()}
|
|
747
|
+
/>
|
|
761
748
|
)}
|
|
762
749
|
{activeTab === 'resources' && (
|
|
763
750
|
<OwnedResources
|
|
@@ -899,7 +886,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
899
886
|
)}
|
|
900
887
|
</div>
|
|
901
888
|
)}
|
|
902
|
-
{upgradeProgress.length === 0 && canHelmWrite &&
|
|
889
|
+
{upgradeProgress.length === 0 && canHelmWrite && (
|
|
903
890
|
<div className="mt-3 border-t border-theme-border pt-3">
|
|
904
891
|
<Tooltip content="Edit the user-supplied Helm values that Radar will pass to this upgrade.">
|
|
905
892
|
<button
|
|
@@ -1067,7 +1054,6 @@ function HelmOperationBanner({
|
|
|
1067
1054
|
releaseNamespace,
|
|
1068
1055
|
managedByFluxHelmRelease,
|
|
1069
1056
|
hookDiagnostics,
|
|
1070
|
-
canCompare,
|
|
1071
1057
|
onCompare,
|
|
1072
1058
|
onNavigateToResource,
|
|
1073
1059
|
}: {
|
|
@@ -1076,7 +1062,6 @@ function HelmOperationBanner({
|
|
|
1076
1062
|
releaseNamespace: string
|
|
1077
1063
|
managedByFluxHelmRelease?: string
|
|
1078
1064
|
hookDiagnostics?: HookDiagnostic[]
|
|
1079
|
-
canCompare?: boolean
|
|
1080
1065
|
onCompare?: (rev1: number, rev2: number) => void
|
|
1081
1066
|
onNavigateToResource?: NavigateToResource
|
|
1082
1067
|
}) {
|
|
@@ -1147,7 +1132,6 @@ function HelmOperationBanner({
|
|
|
1147
1132
|
<OperationInsightSignals
|
|
1148
1133
|
insight={operationInsight}
|
|
1149
1134
|
releaseNamespace={releaseNamespace}
|
|
1150
|
-
canCompare={Boolean(canCompare)}
|
|
1151
1135
|
onCompare={onCompare}
|
|
1152
1136
|
onNavigateToResource={onNavigateToResource}
|
|
1153
1137
|
/>
|
|
@@ -1166,13 +1150,11 @@ function HelmOperationBanner({
|
|
|
1166
1150
|
function OperationInsightSignals({
|
|
1167
1151
|
insight,
|
|
1168
1152
|
releaseNamespace,
|
|
1169
|
-
canCompare,
|
|
1170
1153
|
onCompare,
|
|
1171
1154
|
onNavigateToResource,
|
|
1172
1155
|
}: {
|
|
1173
1156
|
insight?: HelmOperationInsight
|
|
1174
1157
|
releaseNamespace: string
|
|
1175
|
-
canCompare: boolean
|
|
1176
1158
|
onCompare?: (rev1: number, rev2: number) => void
|
|
1177
1159
|
onNavigateToResource?: NavigateToResource
|
|
1178
1160
|
}) {
|
|
@@ -1181,7 +1163,7 @@ function OperationInsightSignals({
|
|
|
1181
1163
|
const relatedCount = primaryResource
|
|
1182
1164
|
? Math.max(0, (insight?.signalCount ?? 0) - 1)
|
|
1183
1165
|
: insight?.relatedResources?.length ?? 0
|
|
1184
|
-
const showCompare = Boolean(compare &&
|
|
1166
|
+
const showCompare = Boolean(compare && onCompare)
|
|
1185
1167
|
if (!primaryResource && !showCompare) {
|
|
1186
1168
|
return null
|
|
1187
1169
|
}
|
|
@@ -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, Input } from '@skyhook-io/k8s-ui'
|
|
3
|
+
import { PaneLoader, Input, useDebouncedValue } from '@skyhook-io/k8s-ui'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
5
5
|
import yaml from 'yaml'
|
|
6
6
|
import { createPatch } from 'diff'
|
|
@@ -66,7 +66,6 @@ export function InstallWizard({ repo, chartName, version, source, repoUrl, defau
|
|
|
66
66
|
const progressEndRef = useRef<HTMLDivElement>(null)
|
|
67
67
|
|
|
68
68
|
const queryClient = useQueryClient()
|
|
69
|
-
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct()
|
|
70
69
|
|
|
71
70
|
// Choose the right data based on source
|
|
72
71
|
const isLocal = source === 'local'
|
|
@@ -209,6 +208,10 @@ export function InstallWizard({ repo, chartName, version, source, repoUrl, defau
|
|
|
209
208
|
)
|
|
210
209
|
const releaseNameError = releaseNameValidation.valid ? null : releaseNameValidation.error
|
|
211
210
|
const namespaceError = namespaceValidation.valid ? null : namespaceValidation.error
|
|
211
|
+
// Checked in the namespace being installed into, once it's a valid name.
|
|
212
|
+
// Debounced: each new namespace is a capabilities round-trip of several SARs.
|
|
213
|
+
const helmActNamespace = useDebouncedValue(namespaceValidation.valid ? namespace.trim() : undefined, 300)
|
|
214
|
+
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct(helmActNamespace)
|
|
212
215
|
const canProceedFromInfo = releaseNameValidation.valid && namespaceValidation.valid
|
|
213
216
|
const canInstall = canProceedFromInfo && !yamlError
|
|
214
217
|
|
|
@@ -2,9 +2,10 @@ import { useState } from 'react'
|
|
|
2
2
|
import { History, Eye, GitCompare, Check, RotateCcw } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import type { HelmOperation, HelmRevision } from '../../types'
|
|
5
|
-
import { getStatusColor, formatDate
|
|
5
|
+
import { getStatusColor, formatDate } from './helm-utils'
|
|
6
6
|
import { SEVERITY_BADGE } from '../../utils/badge-colors'
|
|
7
7
|
import { Tooltip } from '../ui/Tooltip'
|
|
8
|
+
import { formatRelativeAgeTime } from '@skyhook-io/k8s-ui/utils/format'
|
|
8
9
|
|
|
9
10
|
interface RevisionHistoryProps {
|
|
10
11
|
history: HelmRevision[]
|
|
@@ -117,7 +118,7 @@ export function RevisionHistory({ history, currentRevision, operations = [], onV
|
|
|
117
118
|
|
|
118
119
|
<div className="flex items-center gap-4 mt-1 text-xs text-theme-text-tertiary">
|
|
119
120
|
<Tooltip content={revision.updated}><span>{formatDate(revision.updated)}</span></Tooltip>
|
|
120
|
-
<span>{
|
|
121
|
+
<span>{formatRelativeAgeTime(revision.updated)}</span>
|
|
121
122
|
</div>
|
|
122
123
|
|
|
123
124
|
{revision.description && (
|
|
@@ -2,7 +2,7 @@ import { useState, useId } from 'react'
|
|
|
2
2
|
import { DialogPortal } from '@skyhook-io/k8s-ui/components/ui/DialogPortal'
|
|
3
3
|
import { X, Plus, Trash2, Link2, AlertTriangle } from 'lucide-react'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
5
|
-
import { useHelmOCISources, useAddOCISource, useRemoveOCISource, useClusterInfo, useCapabilities } from '../../api/client'
|
|
5
|
+
import { useHelmOCISources, useAddOCISource, useRemoveOCISource, useClusterInfo, useCapabilities, useCloudRole } from '../../api/client'
|
|
6
6
|
import { OperatorManagedNotice } from '../settings/OperatorManagedNotice'
|
|
7
7
|
import { Input } from '@skyhook-io/k8s-ui'
|
|
8
8
|
import type { UpgradeInfo } from '../../types'
|
|
@@ -58,7 +58,10 @@ export function TrackChartSourceDialog({ open, onClose, chartName, sourceIssue,
|
|
|
58
58
|
const removeSource = useRemoveOCISource()
|
|
59
59
|
const { data: capabilities } = useCapabilities()
|
|
60
60
|
const operatorManaged = capabilities?.configManagement === 'operator'
|
|
61
|
-
|
|
61
|
+
// Chart sources are shared configuration that every user's upgrades resolve
|
|
62
|
+
// against, so in Radar Cloud only owners change them (the server enforces it).
|
|
63
|
+
const ownerOnly = !useCloudRole().canAtLeast('owner')
|
|
64
|
+
const canEdit = capabilities != null && !operatorManaged && !ownerOnly
|
|
62
65
|
|
|
63
66
|
// In-cluster Radar has no `helm registry login` store (the pod's HELM_CONFIG_HOME
|
|
64
67
|
// points at an empty /tmp), so private registries can't authenticate — only
|
|
@@ -102,6 +105,11 @@ export function TrackChartSourceDialog({ open, onClose, chartName, sourceIssue,
|
|
|
102
105
|
|
|
103
106
|
<div className="p-4 space-y-4">
|
|
104
107
|
{operatorManaged && <OperatorManagedNotice helmValue="helm.ociSources" />}
|
|
108
|
+
{!operatorManaged && ownerOnly && (
|
|
109
|
+
<p className="text-sm text-theme-text-secondary">
|
|
110
|
+
Only organization owners can add or remove chart sources.
|
|
111
|
+
</p>
|
|
112
|
+
)}
|
|
105
113
|
{sourceIssueCopy && (
|
|
106
114
|
<div className="flex items-start gap-2 rounded-lg border border-theme-border bg-theme-elevated px-3 py-2 text-sm">
|
|
107
115
|
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5 text-theme-text-secondary" />
|
|
@@ -46,7 +46,7 @@ export function ValuesViewer({
|
|
|
46
46
|
|
|
47
47
|
const previewMutation = useHelmPreviewValues()
|
|
48
48
|
const applyMutation = useHelmApplyValues()
|
|
49
|
-
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct()
|
|
49
|
+
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct(namespace)
|
|
50
50
|
const isHistoricalRevision = typeof revision === 'number' && typeof currentRevision === 'number' && revision !== currentRevision
|
|
51
51
|
|
|
52
52
|
const canEdit = Boolean(namespace && name) && canHelmWrite && !isHistoricalRevision
|
|
@@ -177,8 +177,12 @@ export function ClusterHealthCard({
|
|
|
177
177
|
|
|
178
178
|
// Nodes ring segments
|
|
179
179
|
const cordonedCount = counts.nodes.cordoned ?? 0
|
|
180
|
+
const removingCount = counts.nodes.removing ?? 0
|
|
181
|
+
const removingUnhealthyCount = counts.nodes.removingUnhealthy ?? 0
|
|
180
182
|
const nodesRingSegments = [
|
|
181
183
|
{ value: counts.nodes.ready, color: '#22c55e' },
|
|
184
|
+
{ value: removingCount - removingUnhealthyCount, color: 'var(--text-secondary)' },
|
|
185
|
+
{ value: removingUnhealthyCount, color: 'var(--color-error)' },
|
|
182
186
|
{ value: cordonedCount, color: '#eab308' }, // amber for cordoned
|
|
183
187
|
{ value: counts.nodes.notReady, color: '#ef4444' },
|
|
184
188
|
]
|
|
@@ -394,6 +398,9 @@ export function ClusterHealthCard({
|
|
|
394
398
|
{cordonedCount > 0 && (
|
|
395
399
|
<span className="text-yellow-500">{cordonedCount} cordoned</span>
|
|
396
400
|
)}
|
|
401
|
+
{removingCount > 0 && (
|
|
402
|
+
<span className="text-theme-text-secondary">{removingCount} removing{removingUnhealthyCount > 0 ? ` (${removingUnhealthyCount} unhealthy)` : ''}</span>
|
|
403
|
+
)}
|
|
397
404
|
{counts.nodes.notReady > 0 && (
|
|
398
405
|
<span className="text-red-500">{counts.nodes.notReady} not ready</span>
|
|
399
406
|
)}
|
|
@@ -58,7 +58,7 @@ export const MCP_TOOL_CATALOG: MCPToolInfo[] = [
|
|
|
58
58
|
{ arg: 'name', required: true, desc: 'resource name' },
|
|
59
59
|
{ arg: 'namespace', desc: 'omit for cluster-scoped kinds (Node, ClusterRole, IngressClass, etc.)' },
|
|
60
60
|
{ arg: 'group', desc: 'API group when the kind is ambiguous (e.g. serving.knative.dev for Knative Service vs core Service)' },
|
|
61
|
-
{ arg: 'include', desc: 'events, metrics, changes, revisions (rollback targets for Deployment/StatefulSet/DaemonSet/Rollout)' },
|
|
61
|
+
{ arg: 'include', desc: 'events, metrics, changes, issues, drain-plan (Node pod/PDB estimate), revisions (rollback targets for Deployment/StatefulSet/DaemonSet/Rollout)' },
|
|
62
62
|
{ arg: 'context', desc: 'resourceContext tier: basic (default) or none' },
|
|
63
63
|
],
|
|
64
64
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ResourceDetailDrawer as BaseResourceDetailDrawer } from '@skyhook-io/k8s-ui'
|
|
2
2
|
import type { SelectedResource } from '../../types'
|
|
3
3
|
import { WorkloadView } from '../workload/WorkloadView'
|
|
4
|
+
import { CNPGDrawerTrailBack } from '../cnpg/CNPGDrawerTrail'
|
|
4
5
|
|
|
5
6
|
interface ResourceDetailDrawerProps {
|
|
6
7
|
resource: SelectedResource
|
|
@@ -31,6 +32,9 @@ export function ResourceDetailDrawer(props: ResourceDetailDrawerProps) {
|
|
|
31
32
|
return (
|
|
32
33
|
<BaseResourceDetailDrawer {...props}>
|
|
33
34
|
{({ resource, expanded, active, initialTab, onClose, onExpand, onExpandIntent, onCancelExpandIntent, onBack, onNavigateToResource, onCollapseToDrawer }) => (
|
|
35
|
+
<div className="flex h-full min-h-0 flex-col">
|
|
36
|
+
{!expanded && <CNPGDrawerTrailBack resource={resource} />}
|
|
37
|
+
<div className="min-h-0 flex-1">
|
|
34
38
|
<WorkloadView
|
|
35
39
|
kind={resource.kind}
|
|
36
40
|
namespace={resource.namespace}
|
|
@@ -47,6 +51,8 @@ export function ResourceDetailDrawer(props: ResourceDetailDrawerProps) {
|
|
|
47
51
|
onNavigateToResource={onNavigateToResource}
|
|
48
52
|
onCollapseToDrawer={onCollapseToDrawer}
|
|
49
53
|
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
50
56
|
)}
|
|
51
57
|
</BaseResourceDetailDrawer>
|
|
52
58
|
)
|