@skyhook-io/k8s-ui 1.8.9 → 1.8.11
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 -4
- package/src/assets/gitops/argocd.png +0 -0
- package/src/assets/gitops/flux.svg +7 -0
- package/src/components/applications/AppChips.tsx +10 -0
- package/src/components/applications/ApplicationDetail.test.tsx +569 -0
- package/src/components/applications/ApplicationDetail.tsx +2517 -450
- package/src/components/applications/ApplicationsList.tsx +4 -1
- package/src/components/applications/ApplicationsView.tsx +101 -18
- package/src/components/applications/applications-view.test.tsx +47 -0
- package/src/components/applications/index.ts +1 -1
- package/src/components/audit/AuditAlerts.tsx +14 -10
- package/src/components/checks/ChecksView.tsx +81 -51
- package/src/components/checks/severity.ts +39 -45
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -2
- package/src/components/compare/CompareResourcePicker.tsx +2 -2
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +2 -1
- package/src/components/gitops/GitOpsTableView.tsx +14 -59
- package/src/components/gitops/ManagedResourcesList.tsx +8 -11
- package/src/components/gitops/SyncCountdown.tsx +26 -17
- package/src/components/gitops/SyncOptionsDialog.tsx +2 -2
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +277 -104
- package/src/components/gitops/insights/insights-helpers.test.ts +86 -1
- package/src/components/gitops/insights/insights-helpers.ts +69 -1
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +5 -2
- package/src/components/issues/IssuesView.tsx +219 -143
- package/src/components/issues/ResourceIssuesSection.test.tsx +40 -0
- package/src/components/issues/ResourceIssuesSection.tsx +28 -128
- package/src/components/issues/index.ts +3 -2
- package/src/components/issues/issue-timing.ts +53 -0
- package/src/components/issues/issues.test.ts +146 -4
- package/src/components/issues/severity.ts +29 -32
- package/src/components/logs/LogCore.tsx +14 -4
- package/src/components/logs/WorkloadLogsViewer.tsx +31 -6
- package/src/components/logs/useLogStream.ts +8 -1
- package/src/components/namespace-switcher/NamespacePicker.tsx +86 -107
- package/src/components/resources/ResourcesSidebar.test.tsx +107 -0
- package/src/components/resources/ResourcesSidebar.tsx +70 -52
- package/src/components/resources/ResourcesView.selected-kind.test.ts +143 -0
- package/src/components/resources/ResourcesView.tsx +243 -53
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +99 -0
- package/src/components/resources/renderers/JobRenderer.tsx +3 -1
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +47 -0
- package/src/components/resources/renderers/NodeRenderer.test.tsx +103 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +20 -12
- package/src/components/resources/renderers/PVCRenderer.test.tsx +23 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +2 -2
- package/src/components/resources/renderers/PodRenderer.test.tsx +101 -0
- package/src/components/resources/renderers/PodRenderer.tsx +81 -70
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +2 -2
- package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/SecretRenderer.tsx +2 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +58 -40
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowRenderer.test.tsx +43 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +60 -78
- package/src/components/resources/renderers/index.ts +1 -0
- package/src/components/resources/resource-utils-capi.ts +1 -1
- package/src/components/resources/resource-utils.ts +31 -0
- package/src/components/shared/DetailShell.tsx +2 -2
- package/src/components/shared/ResourceActionsBar.tsx +140 -108
- package/src/components/shared/ResourceRendererDispatch.tsx +33 -4
- package/src/components/timeline/TimelineList.tsx +371 -354
- package/src/components/timeline/TimelineStrip.test.tsx +194 -0
- package/src/components/timeline/TimelineStrip.tsx +836 -0
- package/src/components/timeline/TimelineSwimlanes.test.tsx +1028 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +2498 -593
- package/src/components/timeline/TimelineToolbar.test.tsx +320 -0
- package/src/components/timeline/TimelineToolbar.tsx +759 -0
- package/src/components/timeline/TimelineToolbarState.test.tsx +64 -0
- package/src/components/timeline/index.ts +23 -0
- package/src/components/timeline/scrubber-math.test.ts +235 -0
- package/src/components/timeline/scrubber-math.ts +249 -0
- package/src/components/timeline/shared.test.tsx +219 -0
- package/src/components/timeline/shared.tsx +232 -447
- package/src/components/timeline/timeline-filters.test.ts +196 -0
- package/src/components/timeline/timeline-filters.ts +180 -0
- package/src/components/timeline/timeline-lane-sort.test.ts +97 -0
- package/src/components/timeline/timeline-lane-sort.ts +77 -0
- package/src/components/timeline/timeline-live.test.ts +131 -0
- package/src/components/timeline/timeline-live.ts +77 -0
- package/src/components/topology/GroupNode.tsx +9 -2
- package/src/components/topology/K8sResourceNode.test.ts +11 -0
- package/src/components/topology/K8sResourceNode.tsx +387 -243
- package/src/components/topology/TopologyFilterSidebar.tsx +9 -0
- package/src/components/topology/TopologyGraph.tsx +89 -16
- package/src/components/topology/TopologySearch.tsx +2 -2
- package/src/components/topology/layout.ts +10 -2
- package/src/components/topology/topology.css +0 -92
- package/src/components/ui/Badge.tsx +5 -0
- package/src/components/ui/BoardSkeleton.tsx +47 -0
- package/src/components/ui/CardSection.tsx +117 -0
- package/src/components/ui/CodeViewer.tsx +2 -2
- package/src/components/ui/Collapse.tsx +59 -0
- package/src/components/ui/ConfirmDialog.tsx +7 -3
- package/src/components/ui/DialogPortal.tsx +31 -9
- package/src/components/ui/Input.tsx +24 -0
- package/src/components/ui/MultiSelectPicker.test.tsx +101 -0
- package/src/components/ui/MultiSelectPicker.tsx +167 -0
- package/src/components/ui/PaneLoader.tsx +10 -2
- package/src/components/ui/ResourceBar.tsx +68 -25
- package/src/components/ui/SearchBox.tsx +18 -4
- package/src/components/ui/SearchPillInput.tsx +2 -2
- package/src/components/ui/Toast.tsx +1 -1
- package/src/components/ui/drawer-components.tsx +39 -26
- package/src/components/ui/index.ts +6 -0
- package/src/components/ui/severity-tone.ts +63 -0
- package/src/components/workload/ResourceDetailDrawer.tsx +6 -2
- package/src/components/workload/WorkloadView.tsx +2682 -472
- package/src/components/workload/index.ts +1 -1
- package/src/theme/variables.css +15 -1
- package/src/types/core.ts +57 -4
- package/src/utils/api-resources.test.ts +54 -0
- package/src/utils/api-resources.ts +68 -1
- package/src/utils/application-topology.test.ts +198 -0
- package/src/utils/application-topology.ts +261 -0
- package/src/utils/applications.test.ts +167 -1
- package/src/utils/applications.ts +1157 -393
- package/src/utils/index.ts +2 -0
- package/src/utils/navigation.test.ts +32 -1
- package/src/utils/navigation.ts +70 -0
- package/src/utils/resource-hierarchy.test.ts +990 -1
- package/src/utils/resource-hierarchy.ts +905 -267
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/topology-neighborhood.test.ts +202 -1
- package/src/utils/topology-neighborhood.ts +194 -13
- package/src/utils/workflow-execution.test.ts +556 -0
- package/src/utils/workflow-execution.ts +814 -0
- package/src/utils/workload-colors.ts +2 -3
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import { TimelineStrip, resizeWindowWithinQuery, nextWindowRungMs } from './TimelineStrip'
|
|
4
|
+
import type { ScrubberBucket, ScrubberRange } from './scrubber-math'
|
|
5
|
+
|
|
6
|
+
const HOUR = 60 * 60 * 1000
|
|
7
|
+
const query: ScrubberRange = { fromMs: 0, toMs: 24 * HOUR }
|
|
8
|
+
|
|
9
|
+
describe('nextWindowRungMs — consistent round jumps', () => {
|
|
10
|
+
it('steps up to the next round rung', () => {
|
|
11
|
+
expect(nextWindowRungMs(HOUR, 1, 30 * 24 * HOUR)).toBe(2 * HOUR)
|
|
12
|
+
expect(nextWindowRungMs(2 * HOUR, 1, 30 * 24 * HOUR)).toBe(6 * HOUR)
|
|
13
|
+
})
|
|
14
|
+
it('steps down to the previous round rung', () => {
|
|
15
|
+
expect(nextWindowRungMs(6 * HOUR, -1, 30 * 24 * HOUR)).toBe(2 * HOUR)
|
|
16
|
+
expect(nextWindowRungMs(HOUR, -1, 30 * 24 * HOUR)).toBe(30 * 60_000)
|
|
17
|
+
})
|
|
18
|
+
it('snaps an odd dragged size onto the ladder, not doubling it', () => {
|
|
19
|
+
expect(nextWindowRungMs(7.5 * HOUR, 1, 30 * 24 * HOUR)).toBe(12 * HOUR) // not 15h
|
|
20
|
+
expect(nextWindowRungMs(7.5 * HOUR, -1, 30 * 24 * HOUR)).toBe(6 * HOUR)
|
|
21
|
+
})
|
|
22
|
+
it('never exceeds the query-span cap', () => {
|
|
23
|
+
expect(nextWindowRungMs(12 * HOUR, 1, 24 * HOUR)).toBe(24 * HOUR)
|
|
24
|
+
expect(nextWindowRungMs(24 * HOUR, 1, 24 * HOUR)).toBe(24 * HOUR)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('resizeWindowWithinQuery — end-anchored sub-range of the query', () => {
|
|
29
|
+
const window: ScrubberRange = { fromMs: 10 * HOUR, toMs: 14 * HOUR } // 4h ending at 14h
|
|
30
|
+
|
|
31
|
+
it('resizes keeping the END fixed (widening reaches back, narrowing keeps recent)', () => {
|
|
32
|
+
const narrower = resizeWindowWithinQuery(window, 2 * HOUR, query)
|
|
33
|
+
expect(narrower).toEqual({ fromMs: 12 * HOUR, toMs: 14 * HOUR })
|
|
34
|
+
const wider = resizeWindowWithinQuery(window, 8 * HOUR, query)
|
|
35
|
+
expect(wider).toEqual({ fromMs: 6 * HOUR, toMs: 14 * HOUR })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('never grows wider than the query span', () => {
|
|
39
|
+
const next = resizeWindowWithinQuery(window, 100 * HOUR, query)
|
|
40
|
+
expect(next.fromMs).toBe(query.fromMs)
|
|
41
|
+
expect(next.toMs).toBe(query.toMs)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('yields the end only when the start hits the query floor', () => {
|
|
45
|
+
const nearStart: ScrubberRange = { fromMs: HOUR, toMs: 2 * HOUR }
|
|
46
|
+
const next = resizeWindowWithinQuery(nearStart, 4 * HOUR, query)
|
|
47
|
+
expect(next).toEqual({ fromMs: 0, toMs: 4 * HOUR })
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('TimelineStrip bar positioning', () => {
|
|
52
|
+
it('positions sparse buckets by TIME, not by array index', () => {
|
|
53
|
+
// Buckets are sparse (hosts omit empty slots). Index-spacing scattered bars
|
|
54
|
+
// uniformly across the strip; a bar must render at its time's x-position.
|
|
55
|
+
// SSR width defaults to 800; query = 24h, so hour 12 → x = 400.
|
|
56
|
+
const sparse: ScrubberBucket[] = [
|
|
57
|
+
{ startMs: 0, endMs: HOUR, total: 5, warnings: 0 },
|
|
58
|
+
{ startMs: 12 * HOUR, endMs: 13 * HOUR, total: 8, warnings: 0 },
|
|
59
|
+
]
|
|
60
|
+
const html = renderToString(
|
|
61
|
+
<TimelineStrip buckets={sparse} domain={query} selection={query} onSelectionChange={() => {}} />,
|
|
62
|
+
)
|
|
63
|
+
// Time-positioned: second bar at 12/24 of 800px = 400. Index-positioned
|
|
64
|
+
// would have put it at 1 * (800/2) = 400 too — so pin the FIRST bar's width
|
|
65
|
+
// and a three-bucket case instead.
|
|
66
|
+
expect(html).toContain('left:400px')
|
|
67
|
+
const three: ScrubberBucket[] = [
|
|
68
|
+
{ startMs: 0, endMs: HOUR, total: 5, warnings: 0 },
|
|
69
|
+
{ startMs: 2 * HOUR, endMs: 3 * HOUR, total: 3, warnings: 0 },
|
|
70
|
+
{ startMs: 18 * HOUR, endMs: 19 * HOUR, total: 8, warnings: 0 },
|
|
71
|
+
]
|
|
72
|
+
const html3 = renderToString(
|
|
73
|
+
<TimelineStrip buckets={three} domain={query} selection={query} onSelectionChange={() => {}} />,
|
|
74
|
+
)
|
|
75
|
+
// 18h/24h of 800 = 600 (time). Index would give 2*(800/3) ≈ 533.
|
|
76
|
+
expect(html3).toContain('left:600px')
|
|
77
|
+
expect(html3).not.toContain('left:533')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('query pill label — relative when live, absolute when frozen', () => {
|
|
82
|
+
const buckets: ScrubberBucket[] = [{ startMs: 0, endMs: HOUR, total: 3, warnings: 0 }]
|
|
83
|
+
const presets = [{ label: '24h', ms: 24 * HOUR }, { label: 'All', ms: 24 * HOUR * 7 }]
|
|
84
|
+
|
|
85
|
+
it('shows "Last 24h" for a live preset-width query (absolute range on hover)', () => {
|
|
86
|
+
const html = renderToString(
|
|
87
|
+
<TimelineStrip
|
|
88
|
+
buckets={buckets} domain={query} selection={query} onSelectionChange={() => {}}
|
|
89
|
+
presets={presets} liveState={{ kind: 'live', latched: true }}
|
|
90
|
+
/>,
|
|
91
|
+
)
|
|
92
|
+
expect(html).toContain('Last 24h')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('shows "Last <width>" for a live non-preset width', () => {
|
|
96
|
+
const html = renderToString(
|
|
97
|
+
<TimelineStrip
|
|
98
|
+
buckets={buckets} domain={query} selection={{ fromMs: query.toMs - 5 * HOUR, toMs: query.toMs }}
|
|
99
|
+
onSelectionChange={() => {}} presets={presets} liveState={{ kind: 'live', latched: true }}
|
|
100
|
+
/>,
|
|
101
|
+
)
|
|
102
|
+
expect(html).toContain('Last 5h')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('keeps absolute stamps when frozen', () => {
|
|
106
|
+
const html = renderToString(
|
|
107
|
+
<TimelineStrip
|
|
108
|
+
buckets={buckets} domain={query} selection={query} onSelectionChange={() => {}}
|
|
109
|
+
presets={presets} liveState={{ kind: 'frozen', asOfMs: query.toMs }}
|
|
110
|
+
/>,
|
|
111
|
+
)
|
|
112
|
+
expect(html).not.toContain('Last 24h')
|
|
113
|
+
expect(html).toContain('—')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('TimelineStrip render', () => {
|
|
118
|
+
const buckets: ScrubberBucket[] = [
|
|
119
|
+
{ startMs: 0, endMs: 12 * HOUR, total: 5, warnings: 1 },
|
|
120
|
+
{ startMs: 12 * HOUR, endMs: 24 * HOUR, total: 8, warnings: 0 },
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
it('labels the Window control with the WINDOW duration, not the query duration', () => {
|
|
124
|
+
const html = renderToString(
|
|
125
|
+
<TimelineStrip
|
|
126
|
+
buckets={buckets}
|
|
127
|
+
domain={query}
|
|
128
|
+
selection={query} // 24h query
|
|
129
|
+
onSelectionChange={() => {}}
|
|
130
|
+
lens={{ fromMs: 10 * HOUR, toMs: 14 * HOUR }} // 4h window
|
|
131
|
+
onLensChange={() => {}}
|
|
132
|
+
/>,
|
|
133
|
+
)
|
|
134
|
+
// The stepper reflects the 4h window (its label rides the tooltip now).
|
|
135
|
+
expect(html).toContain('aria-label="Zoom in"')
|
|
136
|
+
expect(html).toContain('4h')
|
|
137
|
+
expect(html).toContain('strip-histogram')
|
|
138
|
+
expect(html).toContain('strip-lens')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('hides the Zoom control when there is no window (lens)', () => {
|
|
142
|
+
const html = renderToString(
|
|
143
|
+
<TimelineStrip buckets={buckets} domain={query} selection={query} onSelectionChange={() => {}} />,
|
|
144
|
+
)
|
|
145
|
+
expect(html).not.toContain('aria-label="Zoom out"')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('state caption: full range names the state, band always paints', () => {
|
|
150
|
+
const buckets: ScrubberBucket[] = [
|
|
151
|
+
{ startMs: 0, endMs: 12 * HOUR, total: 5, warnings: 0 },
|
|
152
|
+
{ startMs: 12 * HOUR, endMs: 24 * HOUR, total: 8, warnings: 0 },
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
it('renders the ordinary band + the full-range caption when window == range', () => {
|
|
156
|
+
const html = renderToString(
|
|
157
|
+
<TimelineStrip
|
|
158
|
+
buckets={buckets} domain={query} selection={query} onSelectionChange={() => {}}
|
|
159
|
+
lens={query} onLensChange={() => {}}
|
|
160
|
+
/>,
|
|
161
|
+
)
|
|
162
|
+
// The band (fill + grip + handles) stays visible even at full range — the
|
|
163
|
+
// handles-only treatment made the window affordance nearly invisible.
|
|
164
|
+
expect(html).toContain('strip-lens')
|
|
165
|
+
expect(html).toContain('viewing full range')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('renders the band + window-range caption and highlights in-window buckets when narrowed', () => {
|
|
169
|
+
const html = renderToString(
|
|
170
|
+
<TimelineStrip
|
|
171
|
+
buckets={buckets} domain={query} selection={query} onSelectionChange={() => {}}
|
|
172
|
+
lens={{ fromMs: 4 * HOUR, toMs: 8 * HOUR }} onLensChange={() => {}}
|
|
173
|
+
/>,
|
|
174
|
+
)
|
|
175
|
+
expect(html).toContain('strip-lens')
|
|
176
|
+
// The caption shows the window's time range + the exact loaded total (13); the
|
|
177
|
+
// per-window count is NOT rendered here — from hourly buckets it can only be
|
|
178
|
+
// approximate, and the toolbar already owns the exact "Showing" count.
|
|
179
|
+
expect(html).toContain('13 events ·')
|
|
180
|
+
// The in-window bucket (0–12h midpoint 6h is inside 4–8h) highlights accent;
|
|
181
|
+
// the other stays neutral.
|
|
182
|
+
expect(html).toContain('bg-accent/60')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('suffixes the end stamp with "· now" while live', () => {
|
|
186
|
+
const html = renderToString(
|
|
187
|
+
<TimelineStrip
|
|
188
|
+
buckets={buckets} domain={query} selection={query} onSelectionChange={() => {}}
|
|
189
|
+
liveState={{ kind: 'live', latched: true }}
|
|
190
|
+
/>,
|
|
191
|
+
)
|
|
192
|
+
expect(html).toContain('· now')
|
|
193
|
+
})
|
|
194
|
+
})
|