@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.
Files changed (130) hide show
  1. package/package.json +5 -4
  2. package/src/assets/gitops/argocd.png +0 -0
  3. package/src/assets/gitops/flux.svg +7 -0
  4. package/src/components/applications/AppChips.tsx +10 -0
  5. package/src/components/applications/ApplicationDetail.test.tsx +569 -0
  6. package/src/components/applications/ApplicationDetail.tsx +2517 -450
  7. package/src/components/applications/ApplicationsList.tsx +4 -1
  8. package/src/components/applications/ApplicationsView.tsx +101 -18
  9. package/src/components/applications/applications-view.test.tsx +47 -0
  10. package/src/components/applications/index.ts +1 -1
  11. package/src/components/audit/AuditAlerts.tsx +14 -10
  12. package/src/components/checks/ChecksView.tsx +81 -51
  13. package/src/components/checks/severity.ts +39 -45
  14. package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -2
  15. package/src/components/compare/CompareResourcePicker.tsx +2 -2
  16. package/src/components/gitops/GitOpsGraphFilterRail.tsx +2 -1
  17. package/src/components/gitops/GitOpsTableView.tsx +14 -59
  18. package/src/components/gitops/ManagedResourcesList.tsx +8 -11
  19. package/src/components/gitops/SyncCountdown.tsx +26 -17
  20. package/src/components/gitops/SyncOptionsDialog.tsx +2 -2
  21. package/src/components/gitops/insights/GitOpsInsightViews.tsx +277 -104
  22. package/src/components/gitops/insights/insights-helpers.test.ts +86 -1
  23. package/src/components/gitops/insights/insights-helpers.ts +69 -1
  24. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +5 -2
  25. package/src/components/issues/IssuesView.tsx +219 -143
  26. package/src/components/issues/ResourceIssuesSection.test.tsx +40 -0
  27. package/src/components/issues/ResourceIssuesSection.tsx +28 -128
  28. package/src/components/issues/index.ts +3 -2
  29. package/src/components/issues/issue-timing.ts +53 -0
  30. package/src/components/issues/issues.test.ts +146 -4
  31. package/src/components/issues/severity.ts +29 -32
  32. package/src/components/logs/LogCore.tsx +14 -4
  33. package/src/components/logs/WorkloadLogsViewer.tsx +31 -6
  34. package/src/components/logs/useLogStream.ts +8 -1
  35. package/src/components/namespace-switcher/NamespacePicker.tsx +86 -107
  36. package/src/components/resources/ResourcesSidebar.test.tsx +107 -0
  37. package/src/components/resources/ResourcesSidebar.tsx +70 -52
  38. package/src/components/resources/ResourcesView.selected-kind.test.ts +143 -0
  39. package/src/components/resources/ResourcesView.tsx +243 -53
  40. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
  41. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +99 -0
  42. package/src/components/resources/renderers/JobRenderer.tsx +3 -1
  43. package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +47 -0
  44. package/src/components/resources/renderers/NodeRenderer.test.tsx +103 -0
  45. package/src/components/resources/renderers/NodeRenderer.tsx +20 -12
  46. package/src/components/resources/renderers/PVCRenderer.test.tsx +23 -0
  47. package/src/components/resources/renderers/PVCRenderer.tsx +2 -2
  48. package/src/components/resources/renderers/PodRenderer.test.tsx +101 -0
  49. package/src/components/resources/renderers/PodRenderer.tsx +81 -70
  50. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +2 -2
  51. package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
  52. package/src/components/resources/renderers/SecretRenderer.tsx +2 -0
  53. package/src/components/resources/renderers/ServiceRenderer.tsx +58 -40
  54. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
  55. package/src/components/resources/renderers/WorkflowRenderer.test.tsx +43 -0
  56. package/src/components/resources/renderers/WorkflowRenderer.tsx +60 -78
  57. package/src/components/resources/renderers/index.ts +1 -0
  58. package/src/components/resources/resource-utils-capi.ts +1 -1
  59. package/src/components/resources/resource-utils.ts +31 -0
  60. package/src/components/shared/DetailShell.tsx +2 -2
  61. package/src/components/shared/ResourceActionsBar.tsx +140 -108
  62. package/src/components/shared/ResourceRendererDispatch.tsx +33 -4
  63. package/src/components/timeline/TimelineList.tsx +371 -354
  64. package/src/components/timeline/TimelineStrip.test.tsx +194 -0
  65. package/src/components/timeline/TimelineStrip.tsx +836 -0
  66. package/src/components/timeline/TimelineSwimlanes.test.tsx +1028 -0
  67. package/src/components/timeline/TimelineSwimlanes.tsx +2498 -593
  68. package/src/components/timeline/TimelineToolbar.test.tsx +320 -0
  69. package/src/components/timeline/TimelineToolbar.tsx +759 -0
  70. package/src/components/timeline/TimelineToolbarState.test.tsx +64 -0
  71. package/src/components/timeline/index.ts +23 -0
  72. package/src/components/timeline/scrubber-math.test.ts +235 -0
  73. package/src/components/timeline/scrubber-math.ts +249 -0
  74. package/src/components/timeline/shared.test.tsx +219 -0
  75. package/src/components/timeline/shared.tsx +232 -447
  76. package/src/components/timeline/timeline-filters.test.ts +196 -0
  77. package/src/components/timeline/timeline-filters.ts +180 -0
  78. package/src/components/timeline/timeline-lane-sort.test.ts +97 -0
  79. package/src/components/timeline/timeline-lane-sort.ts +77 -0
  80. package/src/components/timeline/timeline-live.test.ts +131 -0
  81. package/src/components/timeline/timeline-live.ts +77 -0
  82. package/src/components/topology/GroupNode.tsx +9 -2
  83. package/src/components/topology/K8sResourceNode.test.ts +11 -0
  84. package/src/components/topology/K8sResourceNode.tsx +387 -243
  85. package/src/components/topology/TopologyFilterSidebar.tsx +9 -0
  86. package/src/components/topology/TopologyGraph.tsx +89 -16
  87. package/src/components/topology/TopologySearch.tsx +2 -2
  88. package/src/components/topology/layout.ts +10 -2
  89. package/src/components/topology/topology.css +0 -92
  90. package/src/components/ui/Badge.tsx +5 -0
  91. package/src/components/ui/BoardSkeleton.tsx +47 -0
  92. package/src/components/ui/CardSection.tsx +117 -0
  93. package/src/components/ui/CodeViewer.tsx +2 -2
  94. package/src/components/ui/Collapse.tsx +59 -0
  95. package/src/components/ui/ConfirmDialog.tsx +7 -3
  96. package/src/components/ui/DialogPortal.tsx +31 -9
  97. package/src/components/ui/Input.tsx +24 -0
  98. package/src/components/ui/MultiSelectPicker.test.tsx +101 -0
  99. package/src/components/ui/MultiSelectPicker.tsx +167 -0
  100. package/src/components/ui/PaneLoader.tsx +10 -2
  101. package/src/components/ui/ResourceBar.tsx +68 -25
  102. package/src/components/ui/SearchBox.tsx +18 -4
  103. package/src/components/ui/SearchPillInput.tsx +2 -2
  104. package/src/components/ui/Toast.tsx +1 -1
  105. package/src/components/ui/drawer-components.tsx +39 -26
  106. package/src/components/ui/index.ts +6 -0
  107. package/src/components/ui/severity-tone.ts +63 -0
  108. package/src/components/workload/ResourceDetailDrawer.tsx +6 -2
  109. package/src/components/workload/WorkloadView.tsx +2682 -472
  110. package/src/components/workload/index.ts +1 -1
  111. package/src/theme/variables.css +15 -1
  112. package/src/types/core.ts +57 -4
  113. package/src/utils/api-resources.test.ts +54 -0
  114. package/src/utils/api-resources.ts +68 -1
  115. package/src/utils/application-topology.test.ts +198 -0
  116. package/src/utils/application-topology.ts +261 -0
  117. package/src/utils/applications.test.ts +167 -1
  118. package/src/utils/applications.ts +1157 -393
  119. package/src/utils/index.ts +2 -0
  120. package/src/utils/navigation.test.ts +32 -1
  121. package/src/utils/navigation.ts +70 -0
  122. package/src/utils/resource-hierarchy.test.ts +990 -1
  123. package/src/utils/resource-hierarchy.ts +905 -267
  124. package/src/utils/resource-icons.ts +2 -0
  125. package/src/utils/topology-neighborhood.test.ts +202 -1
  126. package/src/utils/topology-neighborhood.ts +194 -13
  127. package/src/utils/workflow-execution.test.ts +556 -0
  128. package/src/utils/workflow-execution.ts +814 -0
  129. package/src/utils/workload-colors.ts +2 -3
  130. 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
+ })