@skyhook-io/k8s-ui 1.5.13 → 1.6.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/package.json +4 -4
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -3
- package/src/components/dock/BottomDock.tsx +24 -17
- package/src/components/dock/DockContext.tsx +39 -0
- package/src/components/gitops/GitOpsDetailLayout.tsx +621 -0
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +216 -0
- package/src/components/gitops/GitOpsTableView.tsx +1441 -0
- package/src/components/gitops/RollbackDialog.tsx +117 -0
- package/src/components/gitops/SyncOptionsDialog.tsx +160 -0
- package/src/components/gitops/detail-helpers.test.ts +97 -0
- package/src/components/gitops/detail-helpers.ts +112 -0
- package/src/components/gitops/index.ts +55 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +1456 -0
- package/src/components/gitops/insights/index.ts +6 -0
- package/src/components/gitops/insights/insights-helpers.test.ts +98 -0
- package/src/components/gitops/insights/insights-helpers.ts +99 -0
- package/src/components/gitops/short-cluster-name.test.ts +36 -0
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +799 -0
- package/src/components/gitops/tree/index.ts +6 -0
- package/src/components/gitops/tree/merge.test.ts +240 -0
- package/src/components/gitops/tree/merge.ts +160 -0
- package/src/components/gitops/tree/tree-helpers.ts +42 -0
- package/src/components/resources/ResourcesSidebar.tsx +42 -15
- package/src/components/resources/ResourcesView.tsx +136 -30
- package/src/components/resources/index.ts +1 -1
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -1
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -1
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -1
- package/src/components/resources/renderers/PodRenderer.tsx +4 -3
- package/src/components/resources/renderers/SecretRenderer.tsx +4 -10
- package/src/components/shared/EditableYamlView.tsx +28 -17
- package/src/components/shared/ManagedByChip.tsx +45 -0
- package/src/components/shared/index.ts +1 -0
- package/src/components/timeline/TimelineList.tsx +3 -3
- package/src/components/topology/TopologyGraph.tsx +3 -2
- package/src/components/ui/Tooltip.tsx +10 -1
- package/src/components/ui/drawer-components.tsx +9 -21
- package/src/components/workload/ResourceDetailDrawer.tsx +5 -3
- package/src/components/workload/WorkloadView.tsx +66 -0
- package/src/hooks/useKeyboardShortcuts.tsx +3 -2
- package/src/index.ts +3 -0
- package/src/types/core.ts +48 -6
- package/src/types/gitops-insights.ts +193 -0
- package/src/types/gitops-tree.ts +57 -0
- package/src/types/index.ts +2 -0
- package/src/utils/badge-colors.ts +31 -1
- package/src/utils/format.ts +28 -0
- package/src/utils/gitops-owner.test.ts +95 -0
- package/src/utils/gitops-owner.ts +55 -0
- package/src/utils/gitops-route.test.ts +78 -0
- package/src/utils/gitops-route.ts +104 -0
- package/src/utils/helm-status.test.ts +50 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/navigation.ts +14 -0
- package/src/utils/resource-hierarchy.ts +47 -3
- package/src/utils/yaml.test.ts +101 -0
- package/src/utils/yaml.ts +26 -0
|
@@ -2,6 +2,7 @@ import { useState, useCallback, useEffect, useRef, type ReactNode } from 'react'
|
|
|
2
2
|
import { TRANSITION_DRAWER } from '../../utils/animation'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import type { SelectedResource } from '../../types'
|
|
5
|
+
import { useDockReservedHeight } from '../dock/DockContext'
|
|
5
6
|
|
|
6
7
|
interface ResourceDetailDrawerProps {
|
|
7
8
|
resource: SelectedResource
|
|
@@ -111,11 +112,12 @@ export function ResourceDetailDrawer({ resource, onClose, onNavigate, initialTab
|
|
|
111
112
|
}, [expanded, onNavigateToResource, onNavigate])
|
|
112
113
|
|
|
113
114
|
const headerHeight = headerHeightProp ?? 49
|
|
115
|
+
const dockInset = useDockReservedHeight()
|
|
114
116
|
|
|
115
117
|
return (
|
|
116
118
|
<div
|
|
117
119
|
className={clsx(
|
|
118
|
-
'
|
|
120
|
+
'absolute right-0 bg-theme-surface border-l border-theme-border flex flex-col shadow-drawer z-40',
|
|
119
121
|
TRANSITION_DRAWER,
|
|
120
122
|
isOpen
|
|
121
123
|
? 'translate-x-0 opacity-100'
|
|
@@ -123,9 +125,9 @@ export function ResourceDetailDrawer({ resource, onClose, onNavigate, initialTab
|
|
|
123
125
|
expanded && '!border-l-0',
|
|
124
126
|
)}
|
|
125
127
|
style={{
|
|
126
|
-
width: expanded ? `calc(
|
|
128
|
+
width: expanded ? `calc(100% - ${leftOffset}px)` : drawerWidth,
|
|
127
129
|
top: headerHeight,
|
|
128
|
-
height: `calc(
|
|
130
|
+
height: `calc(100% - ${headerHeight}px - ${dockInset}px)`,
|
|
129
131
|
// Collapse is instant — no animation, content and width snap together.
|
|
130
132
|
// Expand + slide-in/out animate via TRANSITION_DRAWER class.
|
|
131
133
|
...(isCollapsing && { transition: 'none' }),
|
|
@@ -7,6 +7,7 @@ import { useRegisterShortcuts } from '../../hooks/useKeyboardShortcuts'
|
|
|
7
7
|
import { clsx } from 'clsx'
|
|
8
8
|
import {
|
|
9
9
|
ArrowLeft,
|
|
10
|
+
ArrowRight,
|
|
10
11
|
RefreshCw,
|
|
11
12
|
Activity,
|
|
12
13
|
Terminal,
|
|
@@ -22,6 +23,8 @@ import {
|
|
|
22
23
|
import type { TimelineEvent, ResourceRef, Relationships, SelectedResource, ResolvedEnvFrom } from '../../types'
|
|
23
24
|
import type { NavigateToResource } from '../../utils/navigation'
|
|
24
25
|
import { refToSelectedResource, pluralToKind } from '../../utils/navigation'
|
|
26
|
+
import { gitOpsOwnerFromRelationships, type GitOpsOwnerRef } from '../../utils/gitops-owner'
|
|
27
|
+
import { gitOpsRouteForResource } from '../../utils/gitops-route'
|
|
25
28
|
import { isChangeEvent, isHistoricalEvent } from '../../types'
|
|
26
29
|
import { getKindBadgeColor, getHealthBadgeColor } from '../../utils/badge-colors'
|
|
27
30
|
import { buildResourceHierarchy, getAllEventsFromHierarchy, isProblematicEvent, type ResourceLane } from '../../utils/resource-hierarchy'
|
|
@@ -41,6 +44,7 @@ import {
|
|
|
41
44
|
import { ResourceActionsBar } from '../shared/ResourceActionsBar'
|
|
42
45
|
import { EditableYamlView, SaveSuccessAnimation } from '../shared/EditableYamlView'
|
|
43
46
|
import { ResourceRendererDispatch, getResourceStatus, type RendererOverrides } from '../shared/ResourceRendererDispatch'
|
|
47
|
+
import { ManagedByChip } from '../shared/ManagedByChip'
|
|
44
48
|
import { getKindColorOutline, formatKindName } from '../ui/drawer-components'
|
|
45
49
|
|
|
46
50
|
type TabType = 'overview' | 'timeline' | 'logs' | 'metrics' | 'yaml'
|
|
@@ -110,6 +114,24 @@ interface WorkloadViewProps {
|
|
|
110
114
|
/** Called when tab changes (for URL sync etc.) */
|
|
111
115
|
onTabChange?: (tab: TabType) => void
|
|
112
116
|
|
|
117
|
+
// ── GitOps navigation ─────────────────────────────────────────────────────
|
|
118
|
+
/**
|
|
119
|
+
* Open the GitOps detail page for a controller (Argo Application,
|
|
120
|
+
* Flux Kustomization, Flux HelmRelease). The drawer's "Managed by" chip
|
|
121
|
+
* invokes this when the user clicks through; if not provided, the chip
|
|
122
|
+
* is rendered as a non-interactive label so the relationship is still
|
|
123
|
+
* visible (useful for hosts that haven't routed the GitOps tab yet).
|
|
124
|
+
*/
|
|
125
|
+
onOpenGitOpsResource?: (ref: GitOpsOwnerRef) => void
|
|
126
|
+
/**
|
|
127
|
+
* Open the GitOps detail page for the resource itself, when the resource
|
|
128
|
+
* is a portal-classified GitOps CR (Argo Application/ApplicationSet/
|
|
129
|
+
* AppProject, Flux Kustomization/HelmRelease). Wired in addition to
|
|
130
|
+
* `onOpenGitOpsResource` because the URL is derived here from the live
|
|
131
|
+
* resource rather than from owner labels on a managed object.
|
|
132
|
+
*/
|
|
133
|
+
onNavigateGitOpsPath?: (path: string) => void
|
|
134
|
+
|
|
113
135
|
// ── Render props for platform-specific content ───────────────────────────
|
|
114
136
|
/** Render the logs tab content */
|
|
115
137
|
renderLogsTab?: (props: {
|
|
@@ -135,6 +157,10 @@ interface WorkloadViewProps {
|
|
|
135
157
|
/** Duplicate handler — opens create dialog with this resource's YAML */
|
|
136
158
|
onDuplicate?: (params: { kind: string; namespace: string; name: string; yaml: string }) => void
|
|
137
159
|
|
|
160
|
+
// ── Download ─────────────────────────────────────────────────────────────
|
|
161
|
+
/** Forwarded to EditableYamlView; see there. */
|
|
162
|
+
onDownload?: (content: string, mime: string, filename: string) => void
|
|
163
|
+
|
|
138
164
|
// ── ResourceActionsBar props (passed through) ────────────────────────────
|
|
139
165
|
/** All props for the actions bar (forwarded as-is) */
|
|
140
166
|
actionsBarProps?: Record<string, any>
|
|
@@ -186,6 +212,7 @@ export function WorkloadView({
|
|
|
186
212
|
isMetricsAvailable,
|
|
187
213
|
// Duplicate
|
|
188
214
|
onDuplicate,
|
|
215
|
+
onDownload,
|
|
189
216
|
renderOverviewExtra,
|
|
190
217
|
// Actions bar
|
|
191
218
|
actionsBarProps,
|
|
@@ -193,6 +220,9 @@ export function WorkloadView({
|
|
|
193
220
|
rendererOverrides,
|
|
194
221
|
// Pod env expansion
|
|
195
222
|
resolvedEnvFrom,
|
|
223
|
+
// GitOps
|
|
224
|
+
onOpenGitOpsResource,
|
|
225
|
+
onNavigateGitOpsPath,
|
|
196
226
|
}: WorkloadViewProps) {
|
|
197
227
|
// Normalize kind: URL has plural lowercase, internal logic uses singular PascalCase
|
|
198
228
|
const kind = pluralToKind(kindProp)
|
|
@@ -281,6 +311,12 @@ export function WorkloadView({
|
|
|
281
311
|
|
|
282
312
|
// Metadata
|
|
283
313
|
const metadata = useMemo(() => extractMetadata(kind, resource), [kind, resource])
|
|
314
|
+
const gitopsOwner = useMemo(() => gitOpsOwnerFromRelationships(relationships), [relationships])
|
|
315
|
+
// When the resource itself is a portal GitOps CR (Application, Kustomization,
|
|
316
|
+
// HelmRelease, etc.), surface a link to its dedicated GitOps detail page —
|
|
317
|
+
// the drawer's renderer is thorough but the tab has the tree + insights +
|
|
318
|
+
// operations the drawer can't reproduce inline.
|
|
319
|
+
const gitOpsResourcePath = useMemo(() => gitOpsRouteForResource(resource), [resource])
|
|
284
320
|
|
|
285
321
|
// Copy to clipboard
|
|
286
322
|
const copyToClipboard = useCallback((text: string, key: string) => {
|
|
@@ -425,6 +461,14 @@ export function WorkloadView({
|
|
|
425
461
|
</button>
|
|
426
462
|
</div>
|
|
427
463
|
<p className="text-sm text-theme-text-tertiary">{namespace}</p>
|
|
464
|
+
{(gitopsOwner || (gitOpsResourcePath && onNavigateGitOpsPath)) && (
|
|
465
|
+
<div className="mt-1 flex flex-wrap items-center gap-1.5">
|
|
466
|
+
{gitopsOwner && <ManagedByChip owner={gitopsOwner} onOpen={onOpenGitOpsResource} />}
|
|
467
|
+
{gitOpsResourcePath && onNavigateGitOpsPath && (
|
|
468
|
+
<OpenInGitOpsChip onClick={() => onNavigateGitOpsPath(gitOpsResourcePath)} />
|
|
469
|
+
)}
|
|
470
|
+
</div>
|
|
471
|
+
)}
|
|
428
472
|
</div>
|
|
429
473
|
|
|
430
474
|
{/* Actions bar */}
|
|
@@ -451,6 +495,7 @@ export function WorkloadView({
|
|
|
451
495
|
isSaving={isUpdatingResource}
|
|
452
496
|
saveError={updateResourceError}
|
|
453
497
|
onDuplicate={onDuplicate}
|
|
498
|
+
onDownload={onDownload}
|
|
454
499
|
/>
|
|
455
500
|
) : (
|
|
456
501
|
<>
|
|
@@ -527,6 +572,12 @@ export function WorkloadView({
|
|
|
527
572
|
{metadata.find(m => m.label === 'Image') && (
|
|
528
573
|
<span className="truncate max-w-md font-mono text-xs">{metadata.find(m => m.label === 'Image')?.value}</span>
|
|
529
574
|
)}
|
|
575
|
+
{gitopsOwner && (
|
|
576
|
+
<ManagedByChip owner={gitopsOwner} onOpen={onOpenGitOpsResource} variant="block" />
|
|
577
|
+
)}
|
|
578
|
+
{gitOpsResourcePath && onNavigateGitOpsPath && (
|
|
579
|
+
<OpenInGitOpsChip onClick={() => onNavigateGitOpsPath(gitOpsResourcePath)} />
|
|
580
|
+
)}
|
|
530
581
|
{relationships?.owner && (
|
|
531
582
|
<span>Owner: <button onClick={() => onNavigateToResource?.(refToSelectedResource(relationships.owner!))} className="text-blue-500 hover:underline">{relationships.owner.name}</button></span>
|
|
532
583
|
)}
|
|
@@ -675,6 +726,7 @@ export function WorkloadView({
|
|
|
675
726
|
isSaving={isUpdatingResource}
|
|
676
727
|
saveError={updateResourceError}
|
|
677
728
|
onDuplicate={onDuplicate}
|
|
729
|
+
onDownload={onDownload}
|
|
678
730
|
/>
|
|
679
731
|
)}
|
|
680
732
|
</div>
|
|
@@ -725,6 +777,20 @@ function extractMetadata(kind: string, resource: any): { label: string; value: s
|
|
|
725
777
|
// SUB-COMPONENTS
|
|
726
778
|
// ============================================================================
|
|
727
779
|
|
|
780
|
+
function OpenInGitOpsChip({ onClick }: { onClick: () => void }) {
|
|
781
|
+
return (
|
|
782
|
+
<button
|
|
783
|
+
type="button"
|
|
784
|
+
onClick={onClick}
|
|
785
|
+
title="Open this resource in the GitOps tab (tree + insights + ops)"
|
|
786
|
+
className="inline-flex items-center gap-1 rounded border border-skyhook-500/40 bg-skyhook-500/10 px-1.5 py-0.5 text-[11px] font-medium text-skyhook-500 hover:bg-skyhook-500/20 transition-colors"
|
|
787
|
+
>
|
|
788
|
+
Open in GitOps
|
|
789
|
+
<ArrowRight className="h-3 w-3 shrink-0" />
|
|
790
|
+
</button>
|
|
791
|
+
)
|
|
792
|
+
}
|
|
793
|
+
|
|
728
794
|
function TabButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {
|
|
729
795
|
return (
|
|
730
796
|
<button
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createContext, useContext, useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
export type ShortcutScope = 'global' | 'topology' | 'resources' | 'timeline' | 'helm' | 'traffic' | 'drawer'
|
|
3
|
+
export type ShortcutScope = 'global' | 'topology' | 'resources' | 'timeline' | 'helm' | 'gitops' | 'traffic' | 'drawer'
|
|
4
4
|
|
|
5
5
|
// Scope priority: higher number = higher priority (wins when multiple scopes active)
|
|
6
6
|
const SCOPE_PRIORITY: Record<ShortcutScope, number> = {
|
|
@@ -9,11 +9,12 @@ const SCOPE_PRIORITY: Record<ShortcutScope, number> = {
|
|
|
9
9
|
resources: 1,
|
|
10
10
|
timeline: 1,
|
|
11
11
|
helm: 1,
|
|
12
|
+
gitops: 1,
|
|
12
13
|
traffic: 1,
|
|
13
14
|
drawer: 2,
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export type ShortcutCategory = 'Navigation' | 'Search' | 'Resource Actions' | 'Table' | 'General' | 'Topology' | 'Timeline' | 'Helm' | 'Drawer' | 'Dock'
|
|
17
|
+
export type ShortcutCategory = 'Navigation' | 'Search' | 'Resource Actions' | 'Table' | 'General' | 'Topology' | 'Timeline' | 'Helm' | 'GitOps' | 'Drawer' | 'Dock'
|
|
17
18
|
|
|
18
19
|
export interface KeyboardShortcut {
|
|
19
20
|
/** Unique ID for this shortcut */
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,9 @@ export * from './components/logs'
|
|
|
22
22
|
// Timeline
|
|
23
23
|
export * from './components/timeline'
|
|
24
24
|
|
|
25
|
+
// GitOps
|
|
26
|
+
export * from './components/gitops'
|
|
27
|
+
|
|
25
28
|
// Shared components (ResourceRendererDispatch, EditableYamlView, ResourceActionsBar)
|
|
26
29
|
export * from './components/shared'
|
|
27
30
|
|
package/src/types/core.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Topology types matching the Go backend
|
|
2
2
|
|
|
3
|
-
// Per-resource-type RBAC permissions
|
|
3
|
+
// Per-resource-type RBAC permissions. Field names must match the JSON keys
|
|
4
|
+
// produced by ResourcePermissions in internal/k8s/capabilities.go — there
|
|
5
|
+
// is no automated check across the Go/TS boundary.
|
|
4
6
|
export interface ResourcePermissions {
|
|
5
7
|
pods: boolean
|
|
6
8
|
services: boolean
|
|
@@ -12,16 +14,39 @@ export interface ResourcePermissions {
|
|
|
12
14
|
configMaps: boolean
|
|
13
15
|
secrets: boolean
|
|
14
16
|
events: boolean
|
|
15
|
-
|
|
17
|
+
persistentVolumeClaims: boolean
|
|
16
18
|
nodes: boolean
|
|
17
19
|
namespaces: boolean
|
|
18
20
|
jobs: boolean
|
|
19
21
|
cronJobs: boolean
|
|
20
|
-
|
|
22
|
+
horizontalPodAutoscalers: boolean
|
|
23
|
+
persistentVolumes: boolean
|
|
24
|
+
storageClasses: boolean
|
|
25
|
+
podDisruptionBudgets: boolean
|
|
26
|
+
networkPolicies: boolean
|
|
27
|
+
serviceAccounts: boolean
|
|
28
|
+
roles: boolean
|
|
29
|
+
clusterRoles: boolean
|
|
30
|
+
roleBindings: boolean
|
|
31
|
+
clusterRoleBindings: boolean
|
|
32
|
+
limitRanges: boolean
|
|
21
33
|
gateways: boolean
|
|
22
34
|
httpRoutes: boolean
|
|
35
|
+
verticalPodAutoscalers: boolean
|
|
23
36
|
}
|
|
24
37
|
|
|
38
|
+
// Keys in ResourcePermissions that represent optional CRDs Radar can monitor
|
|
39
|
+
// when they're installed in the cluster. A `false` here means "CRD not
|
|
40
|
+
// installed (or RBAC denied)" — NOT "Radar is missing data the user expects",
|
|
41
|
+
// so banners about RBAC restrictions should ignore these keys.
|
|
42
|
+
//
|
|
43
|
+
// Keep in sync with dynamicCapabilityKinds in internal/k8s/capabilities_alignment_test.go.
|
|
44
|
+
export const OPTIONAL_RESOURCE_KINDS: ReadonlyArray<keyof ResourcePermissions> = [
|
|
45
|
+
'gateways',
|
|
46
|
+
'httpRoutes',
|
|
47
|
+
'verticalPodAutoscalers',
|
|
48
|
+
]
|
|
49
|
+
|
|
25
50
|
// Feature capabilities based on RBAC permissions
|
|
26
51
|
export interface Capabilities {
|
|
27
52
|
exec: boolean // Terminal feature (pods/exec)
|
|
@@ -233,6 +258,7 @@ export interface TimelineEvent {
|
|
|
233
258
|
|
|
234
259
|
// Resource identity
|
|
235
260
|
kind: string
|
|
261
|
+
apiVersion?: string // e.g. "apps/v1", "cluster.x-k8s.io/v1beta1"
|
|
236
262
|
namespace: string
|
|
237
263
|
name: string
|
|
238
264
|
uid?: string
|
|
@@ -389,6 +415,7 @@ export interface ResourceRef {
|
|
|
389
415
|
export interface Relationships {
|
|
390
416
|
owner?: ResourceRef
|
|
391
417
|
deployment?: ResourceRef // Grandparent Deployment (for Pods owned by ReplicaSets)
|
|
418
|
+
managedBy?: ResourceRef[] // Topmost meaningful manager(s): GitOps controller (ArgoCD Application / Flux Kustomization / Flux HelmRelease), Helm release, or the topmost K8s owner. Synthesized server-side; replaces client-side detectGitOpsOwner.
|
|
392
419
|
children?: ResourceRef[]
|
|
393
420
|
services?: ResourceRef[]
|
|
394
421
|
ingresses?: ResourceRef[]
|
|
@@ -398,8 +425,11 @@ export interface Relationships {
|
|
|
398
425
|
consumers?: ResourceRef[]
|
|
399
426
|
scalers?: ResourceRef[]
|
|
400
427
|
scaleTarget?: ResourceRef
|
|
401
|
-
|
|
428
|
+
pdbs?: ResourceRef[] // PodDisruptionBudgets protecting this workload
|
|
429
|
+
networkPolicies?: ResourceRef[] // NetworkPolicy / CiliumNetworkPolicy / ClusterNetworkPolicy variants selecting this workload
|
|
402
430
|
pods?: ResourceRef[]
|
|
431
|
+
serviceAccount?: ResourceRef // For Pods: derived from pod.spec.serviceAccountName
|
|
432
|
+
node?: ResourceRef // For scheduled Pods: derived from pod.spec.nodeName
|
|
403
433
|
}
|
|
404
434
|
|
|
405
435
|
// Parsed X.509 certificate metadata (from backend cert parsing)
|
|
@@ -454,6 +484,11 @@ export interface HelmRelease {
|
|
|
454
484
|
resourceHealth?: 'healthy' | 'degraded' | 'unhealthy' | 'unknown'
|
|
455
485
|
healthIssue?: string // Primary issue if unhealthy (e.g., "OOMKilled")
|
|
456
486
|
healthSummary?: string // Brief summary like "2/3 pods ready"
|
|
487
|
+
// When set, this release was installed by Flux's helm-controller — the
|
|
488
|
+
// user should manage it via the named HelmRelease CR (GitOps tab) rather
|
|
489
|
+
// than helm CLI / Radar's Helm view, since changes here would get
|
|
490
|
+
// reverted at the next reconcile. Format: "namespace/name".
|
|
491
|
+
managedByFluxHelmRelease?: string
|
|
457
492
|
}
|
|
458
493
|
|
|
459
494
|
export interface HelmRevision {
|
|
@@ -483,6 +518,9 @@ export interface HelmReleaseDetail {
|
|
|
483
518
|
hooks?: HelmHook[]
|
|
484
519
|
readme?: string
|
|
485
520
|
dependencies?: ChartDependency[]
|
|
521
|
+
// When set, this release was installed by Flux's helm-controller — see
|
|
522
|
+
// HelmRelease.managedByFluxHelmRelease for context. Format: "namespace/name".
|
|
523
|
+
managedByFluxHelmRelease?: string
|
|
486
524
|
}
|
|
487
525
|
|
|
488
526
|
export interface HelmHook {
|
|
@@ -503,6 +541,7 @@ export interface ChartDependency {
|
|
|
503
541
|
|
|
504
542
|
export interface HelmOwnedResource {
|
|
505
543
|
kind: string
|
|
544
|
+
apiVersion?: string // e.g. "apps/v1", "cluster.x-k8s.io/v1beta1"
|
|
506
545
|
name: string
|
|
507
546
|
namespace: string
|
|
508
547
|
status?: string // Running, Pending, Failed, Active, etc.
|
|
@@ -886,8 +925,11 @@ export interface TrafficFilters {
|
|
|
886
925
|
timeRange: string
|
|
887
926
|
}
|
|
888
927
|
|
|
889
|
-
// Main view type now includes 'traffic'
|
|
890
|
-
|
|
928
|
+
// Main view type now includes 'traffic', 'cost', 'audit', 'gitops'.
|
|
929
|
+
// Library consumers (Radar Hub) get all GitOps surfaces — the package
|
|
930
|
+
// IS the public surface, so adding new top-level views must extend
|
|
931
|
+
// this type rather than rely on app-local extensions.
|
|
932
|
+
export type ExtendedMainView = MainView | 'traffic' | 'cost' | 'audit' | 'gitops'
|
|
891
933
|
|
|
892
934
|
// ============================================================================
|
|
893
935
|
// Image Filesystem Types
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
export interface GitOpsInsight {
|
|
2
|
+
summary: GitOpsInsightSummary
|
|
3
|
+
issues?: GitOpsIssue[]
|
|
4
|
+
changes?: GitOpsChange[]
|
|
5
|
+
plan?: GitOpsPlanItem[]
|
|
6
|
+
history?: GitOpsHistoryItem[]
|
|
7
|
+
capabilities?: GitOpsCapabilities
|
|
8
|
+
// Non-fatal reasons the response is incomplete (RBAC short-circuit,
|
|
9
|
+
// controller unreachable, etc.). UI surfaces these so users can tell
|
|
10
|
+
// "no data" from "we couldn't fetch it".
|
|
11
|
+
warnings?: string[]
|
|
12
|
+
partial?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
import type { GitOpsTool } from './gitops'
|
|
16
|
+
|
|
17
|
+
// Closed enums mirroring `pkg/gitops/insights/vocab.go`. Keeping the FE
|
|
18
|
+
// vocabulary in lockstep with the Go side means switches over these fields
|
|
19
|
+
// are exhaustive and wire-contract drift surfaces at compile time instead of
|
|
20
|
+
// at runtime as a missing render branch.
|
|
21
|
+
export type GitOpsScope = 'operation' | 'resource' | 'condition' | 'tree' | 'lifecycle'
|
|
22
|
+
|
|
23
|
+
export type GitOpsCategory =
|
|
24
|
+
| 'Synced'
|
|
25
|
+
| 'OutOfSync'
|
|
26
|
+
| 'Degraded'
|
|
27
|
+
| 'Missing'
|
|
28
|
+
| 'Pruned'
|
|
29
|
+
| 'Hook'
|
|
30
|
+
| 'Progressing'
|
|
31
|
+
| 'Reconciling'
|
|
32
|
+
| 'Suspended'
|
|
33
|
+
| 'Unknown'
|
|
34
|
+
|
|
35
|
+
export type GitOpsDriftSource = 'lastAppliedAnnotation'
|
|
36
|
+
|
|
37
|
+
export interface GitOpsInsightSummary {
|
|
38
|
+
tool: GitOpsTool
|
|
39
|
+
kind: string
|
|
40
|
+
namespace: string
|
|
41
|
+
name: string
|
|
42
|
+
sync?: string
|
|
43
|
+
health?: string
|
|
44
|
+
operationPhase?: string
|
|
45
|
+
// Latest operation status message — surfaced inline in the status strip
|
|
46
|
+
// when an operation is in flight or just failed.
|
|
47
|
+
operationMessage?: string
|
|
48
|
+
source?: string
|
|
49
|
+
targetRevision?: string
|
|
50
|
+
lastRevision?: string
|
|
51
|
+
lastReconcile?: string
|
|
52
|
+
partialReason?: string
|
|
53
|
+
// Human-readable sync mode for the chip in the status strip.
|
|
54
|
+
// Argo: "Manual" | "Auto" | "Auto · prune" | "Auto · self-heal" | "Auto · prune · self-heal"
|
|
55
|
+
// Flux: "Auto" | "Suspended"
|
|
56
|
+
autoSyncMode?: string
|
|
57
|
+
// True when the resource has metadata.deletionTimestamp set. Drives the
|
|
58
|
+
// [Terminating] chip in the title row + disables mutating action buttons.
|
|
59
|
+
// Backend mirrors this guard in pkg/gitops/operations.go so direct API
|
|
60
|
+
// hits also fail with ErrResourceTerminating.
|
|
61
|
+
terminating?: boolean
|
|
62
|
+
// RFC3339 deletion timestamp; used to compute "21d ago" text in the chip
|
|
63
|
+
// tooltip.
|
|
64
|
+
terminationStartedAt?: string
|
|
65
|
+
// Finalizers blocking deletion. When stuck, naming the finalizer points
|
|
66
|
+
// the user at the controller they need to investigate.
|
|
67
|
+
finalizers?: string[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface GitOpsInsightRef {
|
|
71
|
+
group?: string
|
|
72
|
+
kind: string
|
|
73
|
+
namespace?: string
|
|
74
|
+
name: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface GitOpsIssue {
|
|
78
|
+
severity: 'critical' | 'alert' | 'warning' | 'info'
|
|
79
|
+
scope: GitOpsScope
|
|
80
|
+
reason: string
|
|
81
|
+
message: string
|
|
82
|
+
refs?: GitOpsInsightRef[]
|
|
83
|
+
action?: string
|
|
84
|
+
// Plain-English root cause when the message matched a recognized error
|
|
85
|
+
// pattern. Empty for unrecognized messages — UI falls back to the raw message.
|
|
86
|
+
cause?: string
|
|
87
|
+
// Argo retry count parsed from "(retried N times)". 0 = no retry info.
|
|
88
|
+
retryCount?: number
|
|
89
|
+
// True when retry count crossed the "no longer transient" threshold.
|
|
90
|
+
// Drives a stronger visual treatment.
|
|
91
|
+
stuck?: boolean
|
|
92
|
+
// Structured one-click remediation. When present, the failure card renders
|
|
93
|
+
// a contextual action button. Nil when no automated remedy applies — the
|
|
94
|
+
// `action` string still describes the manual path in that case.
|
|
95
|
+
remediation?: GitOpsRemediation
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type GitOpsRemediationKind = 'create-namespace'
|
|
99
|
+
|
|
100
|
+
export interface GitOpsRemediation {
|
|
101
|
+
kind: GitOpsRemediationKind
|
|
102
|
+
target?: string
|
|
103
|
+
hint?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface GitOpsChange {
|
|
107
|
+
ref: GitOpsInsightRef
|
|
108
|
+
category: GitOpsCategory
|
|
109
|
+
sync?: string
|
|
110
|
+
health?: string
|
|
111
|
+
message?: string
|
|
112
|
+
// Per-resource sync failure message (Argo's status.resources[].syncResult).
|
|
113
|
+
// Distinct from `message` (live health). Empty when sync succeeded.
|
|
114
|
+
syncError?: string
|
|
115
|
+
// Sync hook phase: PreSync / PostSync / SyncFail / PostDelete. Empty
|
|
116
|
+
// for non-hook resources.
|
|
117
|
+
hookPhase?: string
|
|
118
|
+
hasDesired: boolean
|
|
119
|
+
hasLive: boolean
|
|
120
|
+
// Structured per-field diff between the desired state (parsed from
|
|
121
|
+
// kubectl.kubernetes.io/last-applied-configuration) and the live spec.
|
|
122
|
+
// Undefined when the diff couldn't be computed (no annotation, SSA-applied
|
|
123
|
+
// resource, Helm-managed). Renderer falls back to the textual explainer
|
|
124
|
+
// when undefined.
|
|
125
|
+
drift?: GitOpsDrift
|
|
126
|
+
// Up to ~5 most recent events involving this resource, newest first.
|
|
127
|
+
// Surfaces the underlying "why is this stuck" cause (ImagePullBackOff,
|
|
128
|
+
// FailedScheduling, FailedMount, webhook denial) inline so the operator
|
|
129
|
+
// doesn't have to drill into the standard resource drawer.
|
|
130
|
+
recentEvents?: GitOpsEventSummary[]
|
|
131
|
+
partial: boolean
|
|
132
|
+
partialNote?: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface GitOpsDrift {
|
|
136
|
+
entries: GitOpsDriftEntry[]
|
|
137
|
+
source: GitOpsDriftSource
|
|
138
|
+
truncated?: boolean
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface GitOpsDriftEntry {
|
|
142
|
+
path: string // e.g. "spec.disruption.expireAfter"
|
|
143
|
+
op: 'added' | 'removed' | 'changed'
|
|
144
|
+
desired?: string // JSON-encoded
|
|
145
|
+
live?: string // JSON-encoded
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface GitOpsEventSummary {
|
|
149
|
+
type: GitOpsEventType
|
|
150
|
+
reason: string
|
|
151
|
+
message: string
|
|
152
|
+
count?: number
|
|
153
|
+
lastTimestamp: string // RFC3339
|
|
154
|
+
reportingComponent?: string
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export type GitOpsEventType = 'Normal' | 'Warning'
|
|
158
|
+
|
|
159
|
+
export interface GitOpsPlanItem {
|
|
160
|
+
ref: GitOpsInsightRef
|
|
161
|
+
phase?: string
|
|
162
|
+
wave?: number
|
|
163
|
+
waveSet?: boolean
|
|
164
|
+
order: number
|
|
165
|
+
hook?: string
|
|
166
|
+
relationship?: string
|
|
167
|
+
status?: string
|
|
168
|
+
blockedBy?: GitOpsInsightRef[]
|
|
169
|
+
notes?: string[]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface GitOpsHistoryItem {
|
|
173
|
+
id?: string
|
|
174
|
+
revision?: string
|
|
175
|
+
deployedAt?: string
|
|
176
|
+
phase?: string
|
|
177
|
+
message?: string
|
|
178
|
+
source?: string
|
|
179
|
+
initiatedBy?: string
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface GitOpsCapabilities {
|
|
183
|
+
sync: boolean
|
|
184
|
+
refresh: boolean
|
|
185
|
+
terminate: boolean
|
|
186
|
+
suspend: boolean
|
|
187
|
+
resume: boolean
|
|
188
|
+
syncWithSource: boolean
|
|
189
|
+
selectiveSync: boolean
|
|
190
|
+
rollback: boolean
|
|
191
|
+
unsupportedReason?: string
|
|
192
|
+
warnings?: string[]
|
|
193
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { HealthStatus } from './core'
|
|
2
|
+
import type { GitOpsHealthStatus, SyncStatus } from './gitops'
|
|
3
|
+
|
|
4
|
+
export type GitOpsTreeTool = 'argocd' | 'fluxcd'
|
|
5
|
+
export type GitOpsTreeNodeRole = 'root' | 'declared' | 'generated' | 'group'
|
|
6
|
+
|
|
7
|
+
export interface GitOpsTreeRef {
|
|
8
|
+
group?: string
|
|
9
|
+
kind: string
|
|
10
|
+
namespace: string
|
|
11
|
+
name: string
|
|
12
|
+
uid?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GitOpsTreeInfoItem {
|
|
16
|
+
name: string
|
|
17
|
+
value: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface GitOpsTreeNode {
|
|
21
|
+
id: string
|
|
22
|
+
ref: GitOpsTreeRef
|
|
23
|
+
role: GitOpsTreeNodeRole
|
|
24
|
+
tool: GitOpsTreeTool
|
|
25
|
+
sync?: SyncStatus
|
|
26
|
+
health?: GitOpsHealthStatus
|
|
27
|
+
topologyStatus?: HealthStatus
|
|
28
|
+
info?: GitOpsTreeInfoItem[]
|
|
29
|
+
resource?: unknown
|
|
30
|
+
groupedNodeIDs?: string[]
|
|
31
|
+
count?: number
|
|
32
|
+
data?: Record<string, unknown>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type GitOpsTreeEdgeType = 'owns' | 'source' | 'dependsOn'
|
|
36
|
+
|
|
37
|
+
export interface GitOpsTreeEdge {
|
|
38
|
+
source: string
|
|
39
|
+
target: string
|
|
40
|
+
type: GitOpsTreeEdgeType
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface GitOpsTreeSummary {
|
|
44
|
+
declared: number
|
|
45
|
+
generated: number
|
|
46
|
+
grouped: number
|
|
47
|
+
degraded: number
|
|
48
|
+
outOfSync: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface GitOpsResourceTree {
|
|
52
|
+
root: GitOpsTreeNode
|
|
53
|
+
nodes: GitOpsTreeNode[]
|
|
54
|
+
edges: GitOpsTreeEdge[]
|
|
55
|
+
warnings?: string[]
|
|
56
|
+
summary?: GitOpsTreeSummary
|
|
57
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -93,6 +93,7 @@ export const SEVERITY_BADGE = BADGE_SEVERITY_COLORS
|
|
|
93
93
|
export const SEVERITY_TEXT = {
|
|
94
94
|
success: 'text-emerald-700 dark:text-emerald-400',
|
|
95
95
|
warning: 'text-amber-700 dark:text-amber-400',
|
|
96
|
+
alert: 'text-orange-700 dark:text-orange-400',
|
|
96
97
|
error: 'text-red-700 dark:text-red-400',
|
|
97
98
|
info: 'text-sky-700 dark:text-sky-400',
|
|
98
99
|
neutral: 'text-theme-text-secondary',
|
|
@@ -102,6 +103,7 @@ export const SEVERITY_TEXT = {
|
|
|
102
103
|
export const SEVERITY_DOT = {
|
|
103
104
|
success: 'bg-emerald-500',
|
|
104
105
|
warning: 'bg-amber-500',
|
|
106
|
+
alert: 'bg-orange-500',
|
|
105
107
|
error: 'bg-red-500',
|
|
106
108
|
info: 'bg-sky-500',
|
|
107
109
|
neutral: 'bg-theme-hover',
|
|
@@ -111,6 +113,7 @@ export const SEVERITY_DOT = {
|
|
|
111
113
|
export const SEVERITY_BORDER = {
|
|
112
114
|
success: 'border-emerald-200 dark:border-emerald-800/40',
|
|
113
115
|
warning: 'border-amber-200 dark:border-amber-800/40',
|
|
116
|
+
alert: 'border-orange-200 dark:border-orange-800/40',
|
|
114
117
|
error: 'border-red-200 dark:border-red-800/40',
|
|
115
118
|
info: 'border-sky-200 dark:border-sky-800/40',
|
|
116
119
|
neutral: 'border-theme-border',
|
|
@@ -123,7 +126,7 @@ export const SEVERITY_BADGE_BORDERED = {
|
|
|
123
126
|
} as const
|
|
124
127
|
|
|
125
128
|
// Severity type
|
|
126
|
-
export type Severity = 'success' | 'warning' | 'error' | 'info' | 'neutral'
|
|
129
|
+
export type Severity = 'success' | 'warning' | 'alert' | 'error' | 'info' | 'neutral'
|
|
127
130
|
|
|
128
131
|
// =============================================================================
|
|
129
132
|
// RESOURCE STATUS COLORS - for K8s resource states
|
|
@@ -216,6 +219,8 @@ export function healthToSeverity(health: string): Severity {
|
|
|
216
219
|
case 'warning':
|
|
217
220
|
case 'pending':
|
|
218
221
|
return 'warning'
|
|
222
|
+
case 'alert':
|
|
223
|
+
return 'alert'
|
|
219
224
|
case 'unhealthy':
|
|
220
225
|
case 'error':
|
|
221
226
|
case 'failed':
|
|
@@ -281,6 +286,31 @@ export function getHelmStatusColor(status: string): string {
|
|
|
281
286
|
return HELM_STATUS_COLORS[statusLower] || 'bg-theme-hover/50 text-theme-text-secondary'
|
|
282
287
|
}
|
|
283
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Helm release statuses where the row UI should signpost the user
|
|
291
|
+
* toward the drawer (history / rollback / logs).
|
|
292
|
+
*
|
|
293
|
+
* Currently `failed` only. The `pending-*` statuses (install /
|
|
294
|
+
* upgrade / rollback) are excluded deliberately: they're Helm's
|
|
295
|
+
* normal in-flight states during every routine operation. Treating
|
|
296
|
+
* them as "actionable" would briefly attach an alarming chevron +
|
|
297
|
+
* tooltip to every install while it ran — indistinguishable from
|
|
298
|
+
* the genuinely-stuck case (controller crashed mid-flight). Until
|
|
299
|
+
* we have release age available client-side to disambiguate
|
|
300
|
+
* "in-flight" from "stuck > N min", we give up the stuck-detect
|
|
301
|
+
* signpost rather than wrongly alarm the common case.
|
|
302
|
+
*
|
|
303
|
+
* @see https://github.com/helm/helm/blob/dev-v3/pkg/release/status.go
|
|
304
|
+
*/
|
|
305
|
+
const ACTIONABLE_HELM_STATUSES: ReadonlySet<string> = new Set([
|
|
306
|
+
'failed',
|
|
307
|
+
])
|
|
308
|
+
|
|
309
|
+
export function isHelmReleaseActionable(status: string | null | undefined): boolean {
|
|
310
|
+
if (!status) return false
|
|
311
|
+
return ACTIONABLE_HELM_STATUSES.has(status.toLowerCase())
|
|
312
|
+
}
|
|
313
|
+
|
|
284
314
|
// =============================================================================
|
|
285
315
|
// VULNERABILITY SEVERITY COLORS - for Trivy and other security scanners
|
|
286
316
|
// =============================================================================
|