@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.
Files changed (57) hide show
  1. package/package.json +4 -4
  2. package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -3
  3. package/src/components/dock/BottomDock.tsx +24 -17
  4. package/src/components/dock/DockContext.tsx +39 -0
  5. package/src/components/gitops/GitOpsDetailLayout.tsx +621 -0
  6. package/src/components/gitops/GitOpsGraphFilterRail.tsx +216 -0
  7. package/src/components/gitops/GitOpsTableView.tsx +1441 -0
  8. package/src/components/gitops/RollbackDialog.tsx +117 -0
  9. package/src/components/gitops/SyncOptionsDialog.tsx +160 -0
  10. package/src/components/gitops/detail-helpers.test.ts +97 -0
  11. package/src/components/gitops/detail-helpers.ts +112 -0
  12. package/src/components/gitops/index.ts +55 -0
  13. package/src/components/gitops/insights/GitOpsInsightViews.tsx +1456 -0
  14. package/src/components/gitops/insights/index.ts +6 -0
  15. package/src/components/gitops/insights/insights-helpers.test.ts +98 -0
  16. package/src/components/gitops/insights/insights-helpers.ts +99 -0
  17. package/src/components/gitops/short-cluster-name.test.ts +36 -0
  18. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +799 -0
  19. package/src/components/gitops/tree/index.ts +6 -0
  20. package/src/components/gitops/tree/merge.test.ts +240 -0
  21. package/src/components/gitops/tree/merge.ts +160 -0
  22. package/src/components/gitops/tree/tree-helpers.ts +42 -0
  23. package/src/components/resources/ResourcesSidebar.tsx +42 -15
  24. package/src/components/resources/ResourcesView.tsx +136 -30
  25. package/src/components/resources/index.ts +1 -1
  26. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -1
  27. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -1
  28. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -1
  29. package/src/components/resources/renderers/PodRenderer.tsx +4 -3
  30. package/src/components/resources/renderers/SecretRenderer.tsx +4 -10
  31. package/src/components/shared/EditableYamlView.tsx +28 -17
  32. package/src/components/shared/ManagedByChip.tsx +45 -0
  33. package/src/components/shared/index.ts +1 -0
  34. package/src/components/timeline/TimelineList.tsx +3 -3
  35. package/src/components/topology/TopologyGraph.tsx +3 -2
  36. package/src/components/ui/Tooltip.tsx +10 -1
  37. package/src/components/ui/drawer-components.tsx +9 -21
  38. package/src/components/workload/ResourceDetailDrawer.tsx +5 -3
  39. package/src/components/workload/WorkloadView.tsx +66 -0
  40. package/src/hooks/useKeyboardShortcuts.tsx +3 -2
  41. package/src/index.ts +3 -0
  42. package/src/types/core.ts +48 -6
  43. package/src/types/gitops-insights.ts +193 -0
  44. package/src/types/gitops-tree.ts +57 -0
  45. package/src/types/index.ts +2 -0
  46. package/src/utils/badge-colors.ts +31 -1
  47. package/src/utils/format.ts +28 -0
  48. package/src/utils/gitops-owner.test.ts +95 -0
  49. package/src/utils/gitops-owner.ts +55 -0
  50. package/src/utils/gitops-route.test.ts +78 -0
  51. package/src/utils/gitops-route.ts +104 -0
  52. package/src/utils/helm-status.test.ts +50 -0
  53. package/src/utils/index.ts +2 -0
  54. package/src/utils/navigation.ts +14 -0
  55. package/src/utils/resource-hierarchy.ts +47 -3
  56. package/src/utils/yaml.test.ts +101 -0
  57. package/src/utils/yaml.ts +26 -0
@@ -0,0 +1,216 @@
1
+ import { CheckCircle2, CircleDot, GitBranch, HeartPulse, LayoutGrid, List, Search } from 'lucide-react'
2
+
3
+ import { GitOpsFacetButton, GitOpsFilterSection } from './GitOpsTableView'
4
+ import type { GitOpsResourceTree } from '../../types/gitops-tree'
5
+ import type { GitOpsTreePreset } from './tree'
6
+
7
+ // =============================================================================
8
+ // GitOpsGraphFilterRail — the left-side filter sidebar that pairs with
9
+ // <GitOpsTreeGraph> in the Topology tab of the GitOps detail page.
10
+ //
11
+ // Originally inline in OSS radar/web/src/components/gitops/GitOpsView.tsx;
12
+ // extracted here so Radar Hub's fleet detail page can mount the same rail
13
+ // next to the same graph. Stateless — caller owns preset/search/filter
14
+ // state and passes it down; click handlers thread back through callbacks.
15
+ //
16
+ // buildTreeFacets is the data-prep helper that turns a ResourceTree into
17
+ // the facet count buckets the rail consumes. Exported so callers can
18
+ // build it once + pass into both this rail and any sibling display.
19
+ // =============================================================================
20
+
21
+ export interface GitOpsTreeFacets {
22
+ kinds: Array<{ name: string; count: number }>
23
+ sync: Array<{ name: string; count: number }>
24
+ health: Array<{ name: string; count: number }>
25
+ namespaces: Array<{ name: string; count: number }>
26
+ roles: Array<{ name: string; count: number }>
27
+ }
28
+
29
+ export function buildTreeFacets(tree: GitOpsResourceTree | null | undefined): GitOpsTreeFacets {
30
+ const nodes = tree?.nodes ?? []
31
+ return {
32
+ kinds: countValues(
33
+ nodes.filter((n) => n.role !== 'group').map((n) => n.ref.kind).filter(Boolean),
34
+ ),
35
+ sync: countValues(nodes.map((n) => n.sync || 'Unknown')),
36
+ health: countValues(nodes.map((n) => n.health || 'Unknown')),
37
+ namespaces: countValues(nodes.map((n) => n.ref.namespace || '(cluster)')),
38
+ roles: countValues(nodes.map((n) => n.role)),
39
+ }
40
+ }
41
+
42
+ export interface GitOpsGraphFilterRailProps {
43
+ facets: GitOpsTreeFacets
44
+ preset: GitOpsTreePreset
45
+ onPresetChange: (preset: GitOpsTreePreset) => void
46
+ search: string
47
+ onSearchChange: (value: string) => void
48
+ kinds: Set<string>
49
+ onToggleKind: (value: string) => void
50
+ sync: Set<string>
51
+ onToggleSync: (value: string) => void
52
+ health: Set<string>
53
+ onToggleHealth: (value: string) => void
54
+ namespaces: Set<string>
55
+ onToggleNamespace: (value: string) => void
56
+ roles: Set<string>
57
+ onToggleRole: (value: string) => void
58
+ }
59
+
60
+ export function GitOpsGraphFilterRail({
61
+ facets,
62
+ preset,
63
+ onPresetChange,
64
+ search,
65
+ onSearchChange,
66
+ kinds,
67
+ onToggleKind,
68
+ sync,
69
+ onToggleSync,
70
+ health,
71
+ onToggleHealth,
72
+ namespaces,
73
+ onToggleNamespace,
74
+ roles,
75
+ onToggleRole,
76
+ }: GitOpsGraphFilterRailProps) {
77
+ return (
78
+ <aside className="min-h-0 overflow-y-auto bg-theme-surface/90 max-lg:h-48 max-lg:max-h-48">
79
+ <div className="border-b border-theme-border px-3 py-3">
80
+ <div className="relative">
81
+ <Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-theme-text-tertiary" />
82
+ <input
83
+ value={search}
84
+ onChange={(event) => onSearchChange(event.target.value)}
85
+ placeholder="Filter resources..."
86
+ className="h-8 w-full rounded-md border border-theme-border bg-theme-base pl-8 pr-3 text-sm text-theme-text-primary placeholder:text-theme-text-tertiary focus:outline-none focus:ring-1 focus:ring-blue-500/50"
87
+ />
88
+ </div>
89
+ </div>
90
+ <GitOpsFilterSection icon={GitBranch} title="Graph">
91
+ <div className="grid grid-cols-2 gap-1">
92
+ {(['compact', 'workloads', 'app', 'full'] as GitOpsTreePreset[]).map((value) => (
93
+ <button
94
+ key={value}
95
+ type="button"
96
+ onClick={() => onPresetChange(value)}
97
+ className={`rounded-md px-2 py-1.5 text-left text-[11px] font-medium transition-colors ${
98
+ preset === value
99
+ ? 'bg-skyhook-500 text-white'
100
+ : 'bg-theme-elevated text-theme-text-secondary hover:bg-theme-hover hover:text-theme-text-primary'
101
+ }`}
102
+ >
103
+ {value === 'app' ? 'Declared' : value[0].toUpperCase() + value.slice(1)}
104
+ </button>
105
+ ))}
106
+ </div>
107
+ </GitOpsFilterSection>
108
+ <GitOpsFilterSection icon={List} title="Kinds">
109
+ {facets.kinds.slice(0, 14).map((item) => (
110
+ <GitOpsFacetButton
111
+ key={item.name}
112
+ label={item.name}
113
+ count={item.count}
114
+ active={kinds.has(item.name)}
115
+ onClick={() => onToggleKind(item.name)}
116
+ />
117
+ ))}
118
+ </GitOpsFilterSection>
119
+ <GitOpsFilterSection icon={CheckCircle2} title="Sync">
120
+ {facets.sync.map((item) => (
121
+ <GitOpsFacetButton
122
+ key={item.name}
123
+ label={item.name}
124
+ count={item.count}
125
+ active={sync.has(item.name)}
126
+ tone={syncTone(item.name)}
127
+ onClick={() => onToggleSync(item.name)}
128
+ />
129
+ ))}
130
+ </GitOpsFilterSection>
131
+ <GitOpsFilterSection icon={HeartPulse} title="Health">
132
+ {facets.health.map((item) => (
133
+ <GitOpsFacetButton
134
+ key={item.name}
135
+ label={item.name}
136
+ count={item.count}
137
+ active={health.has(item.name)}
138
+ tone={healthTone(item.name)}
139
+ onClick={() => onToggleHealth(item.name)}
140
+ />
141
+ ))}
142
+ </GitOpsFilterSection>
143
+ <GitOpsFilterSection icon={CircleDot} title="Role">
144
+ {facets.roles.map((item) => (
145
+ <GitOpsFacetButton
146
+ key={item.name}
147
+ label={roleLabel(item.name)}
148
+ count={item.count}
149
+ active={roles.has(item.name)}
150
+ onClick={() => onToggleRole(item.name)}
151
+ />
152
+ ))}
153
+ </GitOpsFilterSection>
154
+ <GitOpsFilterSection icon={LayoutGrid} title="Namespaces">
155
+ {facets.namespaces.slice(0, 12).map((item) => (
156
+ <GitOpsFacetButton
157
+ key={item.name}
158
+ label={item.name}
159
+ count={item.count}
160
+ active={namespaces.has(item.name)}
161
+ onClick={() => onToggleNamespace(item.name)}
162
+ />
163
+ ))}
164
+ </GitOpsFilterSection>
165
+ </aside>
166
+ )
167
+ }
168
+
169
+ // ----- Helpers shared with consumers -----
170
+
171
+ // toggleSet is the standard Set<string> add/remove pair used to back the
172
+ // rail's facet click handlers. Caller owns the Set state; this just keeps
173
+ // the call-site terse.
174
+ export function toggleSet(set: Set<string>, setter: (next: Set<string>) => void, value: string) {
175
+ const next = new Set(set)
176
+ if (next.has(value)) next.delete(value)
177
+ else next.add(value)
178
+ setter(next)
179
+ }
180
+
181
+ function countValues(values: string[]): Array<{ name: string; count: number }> {
182
+ const map = new Map<string, number>()
183
+ for (const v of values) {
184
+ if (!v) continue
185
+ map.set(v, (map.get(v) ?? 0) + 1)
186
+ }
187
+ return Array.from(map.entries())
188
+ .map(([name, count]) => ({ name, count }))
189
+ .sort((a, b) => b.count - a.count)
190
+ }
191
+
192
+ function syncTone(value: string): 'neutral' | 'success' | 'warning' | 'error' | 'info' {
193
+ if (value === 'Synced') return 'success'
194
+ if (value === 'OutOfSync') return 'warning'
195
+ if (value === 'Reconciling') return 'info'
196
+ return 'neutral'
197
+ }
198
+
199
+ function healthTone(value: string): 'neutral' | 'success' | 'warning' | 'error' | 'info' {
200
+ if (value === 'Healthy') return 'success'
201
+ if (value === 'Degraded' || value === 'Missing') return 'error'
202
+ if (value === 'Progressing') return 'info'
203
+ if (value === 'Suspended') return 'warning'
204
+ return 'neutral'
205
+ }
206
+
207
+ function roleLabel(value: string): string {
208
+ return (
209
+ {
210
+ root: 'Root',
211
+ declared: 'Declared',
212
+ generated: 'Generated',
213
+ group: 'Groups',
214
+ } as Record<string, string>
215
+ )[value] ?? value
216
+ }