@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
@@ -1,6 +1,22 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { SEVERITY_DOT } from '../../../utils/badge-colors'
3
- import { compactSource, entryTone, gitopsToSeverity, messageToPhase, phaseToTone } from './insights-helpers'
3
+ import type { GitOpsChange } from '../../../types'
4
+ import {
5
+ changeMatchesFacets,
6
+ changeMatchesSearch,
7
+ compactSource,
8
+ entryTone,
9
+ gitopsToSeverity,
10
+ healthStatusRank,
11
+ messageToPhase,
12
+ phaseToTone,
13
+ resourceStatusCounts,
14
+ syncStatusRank,
15
+ } from './insights-helpers'
16
+
17
+ function change(partial: Partial<GitOpsChange> & { ref: GitOpsChange['ref'] }): GitOpsChange {
18
+ return { category: 'Unknown', hasDesired: true, hasLive: true, partial: false, ...partial }
19
+ }
4
20
 
5
21
  describe('gitopsToSeverity', () => {
6
22
  it.each([
@@ -96,3 +112,72 @@ describe('compactSource', () => {
96
112
  expect(compactSource('')).toBe('')
97
113
  })
98
114
  })
115
+
116
+ describe('changeMatchesSearch', () => {
117
+ const c = change({ ref: { kind: 'Deployment', name: 'guestbook-ui', namespace: 'demo', group: 'apps' } })
118
+ it('matches on kind, name, namespace, and group (case-insensitive)', () => {
119
+ expect(changeMatchesSearch(c, 'deploy')).toBe(true)
120
+ expect(changeMatchesSearch(c, 'GUESTBOOK')).toBe(true)
121
+ expect(changeMatchesSearch(c, 'demo')).toBe(true)
122
+ expect(changeMatchesSearch(c, 'apps')).toBe(true)
123
+ })
124
+ it('empty/whitespace query matches everything', () => {
125
+ expect(changeMatchesSearch(c, '')).toBe(true)
126
+ expect(changeMatchesSearch(c, ' ')).toBe(true)
127
+ })
128
+ it('non-matching query is filtered out', () => {
129
+ expect(changeMatchesSearch(c, 'nomatch')).toBe(false)
130
+ })
131
+ })
132
+
133
+ describe('changeMatchesFacets', () => {
134
+ const outOfSync = change({ ref: { kind: 'Service', name: 'a' }, sync: 'OutOfSync', health: 'Healthy' })
135
+ const degraded = change({ ref: { kind: 'Deployment', name: 'b' }, sync: 'Synced', health: 'Degraded' })
136
+ const missing = change({ ref: { kind: 'Secret', name: 'c' }, sync: 'OutOfSync', health: 'Missing' })
137
+ const healthy = change({ ref: { kind: 'ConfigMap', name: 'd' }, sync: 'Synced', health: 'Healthy' })
138
+
139
+ it('empty facet set matches everything', () => {
140
+ const none = new Set<never>()
141
+ expect(changeMatchesFacets(outOfSync, none)).toBe(true)
142
+ expect(changeMatchesFacets(healthy, none)).toBe(true)
143
+ })
144
+ it('outOfSync keys off sync status', () => {
145
+ const f = new Set(['outOfSync'] as const)
146
+ expect(changeMatchesFacets(outOfSync, f)).toBe(true)
147
+ expect(changeMatchesFacets(degraded, f)).toBe(false)
148
+ })
149
+ it('degraded/missing key off health status', () => {
150
+ expect(changeMatchesFacets(degraded, new Set(['degraded'] as const))).toBe(true)
151
+ expect(changeMatchesFacets(missing, new Set(['missing'] as const))).toBe(true)
152
+ expect(changeMatchesFacets(healthy, new Set(['degraded'] as const))).toBe(false)
153
+ })
154
+ it('multiple facets union (OR)', () => {
155
+ const f = new Set(['degraded', 'missing'] as const)
156
+ expect(changeMatchesFacets(degraded, f)).toBe(true)
157
+ expect(changeMatchesFacets(missing, f)).toBe(true)
158
+ expect(changeMatchesFacets(outOfSync, f)).toBe(false)
159
+ })
160
+ })
161
+
162
+ describe('resourceStatusCounts', () => {
163
+ it('counts each facet independently (a resource can be both OutOfSync and Missing)', () => {
164
+ const counts = resourceStatusCounts([
165
+ change({ ref: { kind: 'Service', name: 'a' }, sync: 'OutOfSync', health: 'Healthy' }),
166
+ change({ ref: { kind: 'Secret', name: 'c' }, sync: 'OutOfSync', health: 'Missing' }),
167
+ change({ ref: { kind: 'Deployment', name: 'b' }, sync: 'Synced', health: 'Degraded' }),
168
+ change({ ref: { kind: 'ConfigMap', name: 'd' }, sync: 'Synced', health: 'Healthy' }),
169
+ ])
170
+ expect(counts).toEqual({ outOfSync: 2, degraded: 1, missing: 1 })
171
+ })
172
+ })
173
+
174
+ describe('syncStatusRank / healthStatusRank', () => {
175
+ it('ascending sync rank surfaces OutOfSync before Synced', () => {
176
+ expect(syncStatusRank('OutOfSync')).toBeLessThan(syncStatusRank('Synced'))
177
+ expect(syncStatusRank('OutOfSync')).toBeLessThan(syncStatusRank('Unknown'))
178
+ })
179
+ it('ascending health rank surfaces Missing/Degraded before Healthy', () => {
180
+ expect(healthStatusRank('Missing')).toBeLessThan(healthStatusRank('Degraded'))
181
+ expect(healthStatusRank('Degraded')).toBeLessThan(healthStatusRank('Healthy'))
182
+ })
183
+ })
@@ -2,7 +2,7 @@
2
2
  // GitOpsInsightViews.tsx so they can be unit-tested without importing JSX.
3
3
 
4
4
  import { SEVERITY_DOT, type Severity } from '../../../utils/badge-colors'
5
- import type { GitOpsHistoryItem } from '../../../types'
5
+ import type { GitOpsChange, GitOpsHistoryItem } from '../../../types'
6
6
  import type { SyncStatus, GitOpsHealthStatus } from '../../../types/gitops'
7
7
 
8
8
  const SYNC_STATUS_SET = new Set<SyncStatus>(['Synced', 'OutOfSync', 'Reconciling', 'Unknown'])
@@ -23,6 +23,74 @@ export function normalizeHealthStatus(value: string | undefined | null): GitOpsH
23
23
  return 'Unknown'
24
24
  }
25
25
 
26
+ // --- Resources table: filtering + sorting -------------------------------
27
+ // Pure predicates + rank functions backing the Resources list's search box,
28
+ // status facets, and sortable columns. Kept here (not inline in the view) so
29
+ // the sort/filter behavior is unit-testable without rendering.
30
+
31
+ // The status facets an operator filters the Resources list by — the three
32
+ // states worth isolating on a broadly-drifted app. (These replaced the
33
+ // per-resource OutOfSync "issues" that used to restate the whole table.)
34
+ export type ResourceStatusFacet = 'outOfSync' | 'degraded' | 'missing'
35
+
36
+ export type ResourceSortKey = 'order' | 'name' | 'sync' | 'health'
37
+
38
+ // changeSync/changeHealth normalize a change's raw backend strings onto the
39
+ // badge unions. `sync` falls back to `category` to match how ChangeRow and
40
+ // the sync badge resolve it.
41
+ export function changeSync(change: GitOpsChange): SyncStatus {
42
+ return normalizeSyncStatus(change.sync ?? change.category)
43
+ }
44
+
45
+ export function changeHealth(change: GitOpsChange): GitOpsHealthStatus {
46
+ return normalizeHealthStatus(change.health)
47
+ }
48
+
49
+ // Rank sync/health so an ascending sort surfaces the states an operator cares
50
+ // about first (drift, then failures). Mirrors the GitOps application table's
51
+ // syncRank/healthRank so the two tables order status the same way.
52
+ export function syncStatusRank(sync: SyncStatus): number {
53
+ return ({ OutOfSync: 0, Reconciling: 1, Unknown: 2, Synced: 3 } as Record<SyncStatus, number>)[sync]
54
+ }
55
+
56
+ export function healthStatusRank(health: GitOpsHealthStatus): number {
57
+ return ({ Missing: 0, Degraded: 1, Progressing: 2, Unknown: 3, Suspended: 4, Healthy: 5 } as Record<GitOpsHealthStatus, number>)[health]
58
+ }
59
+
60
+ export function changeMatchesSearch(change: GitOpsChange, query: string): boolean {
61
+ const q = query.trim().toLowerCase()
62
+ if (!q) return true
63
+ const { kind, name, namespace, group } = change.ref
64
+ return [kind, name, namespace, group].some((s) => s?.toLowerCase().includes(q))
65
+ }
66
+
67
+ // Union match: a change passes when it satisfies ANY active facet (empty set
68
+ // = no filter). OutOfSync keys off sync status; Degraded/Missing off health.
69
+ export function changeMatchesFacets(change: GitOpsChange, facets: Set<ResourceStatusFacet>): boolean {
70
+ if (facets.size === 0) return true
71
+ if (facets.has('outOfSync') && changeSync(change) === 'OutOfSync') return true
72
+ if (facets.has('degraded') && changeHealth(change) === 'Degraded') return true
73
+ if (facets.has('missing') && changeHealth(change) === 'Missing') return true
74
+ return false
75
+ }
76
+
77
+ export interface ResourceStatusCounts {
78
+ outOfSync: number
79
+ degraded: number
80
+ missing: number
81
+ }
82
+
83
+ export function resourceStatusCounts(changes: GitOpsChange[]): ResourceStatusCounts {
84
+ const counts: ResourceStatusCounts = { outOfSync: 0, degraded: 0, missing: 0 }
85
+ for (const c of changes) {
86
+ if (changeSync(c) === 'OutOfSync') counts.outOfSync++
87
+ const health = changeHealth(c)
88
+ if (health === 'Degraded') counts.degraded++
89
+ if (health === 'Missing') counts.missing++
90
+ }
91
+ return counts
92
+ }
93
+
26
94
  // Map GitOps-flavored vocabulary (Argo/Flux phase strings, insight Issue
27
95
  // severities) onto the canonical Severity tokens used by SEVERITY_BADGE /
28
96
  // SEVERITY_TEXT / SEVERITY_DOT. Centralizing this keeps call sites from
@@ -17,12 +17,14 @@ import {
17
17
  import '@xyflow/react/dist/style.css'
18
18
  import { AlertTriangle, ChevronRight, Maximize, Search, X } from 'lucide-react'
19
19
  import { PaneLoader } from '../../ui/PaneLoader'
20
+ import { Input } from '../../ui/Input'
20
21
  import { clsx } from 'clsx'
21
22
 
22
23
  import type { GitOpsResourceTree, GitOpsTreeNode, GitOpsTreeRef, HealthStatus } from '../../../types'
23
24
  import { displayKind } from '../../../types'
24
25
  import { healthToSeverity, SEVERITY_DOT } from '../../../utils/badge-colors'
25
26
  import { formatCompactAge } from '../../../utils/format'
27
+ import { getTopologyIcon } from '../../../utils/resource-icons'
26
28
  import { Tooltip } from '../../ui/Tooltip'
27
29
  import { hasGitOpsTreeFilters, matchesGitOpsTreeFilters, type GitOpsTreeFilters } from './tree-helpers'
28
30
 
@@ -252,7 +254,7 @@ function GitOpsTreeToolbar({
252
254
  </div>
253
255
  <div className="flex items-center gap-1 rounded-lg border border-theme-border bg-theme-surface/90 px-2 py-1.5 backdrop-blur">
254
256
  <Search className="h-3.5 w-3.5 text-theme-text-tertiary" />
255
- <input
257
+ <Input
256
258
  value={query}
257
259
  onChange={(event) => onQueryChange(event.target.value)}
258
260
  placeholder="Find node..."
@@ -549,6 +551,7 @@ const GitOpsResourceNode = memo(function GitOpsResourceNode({ data }: NodeProps<
549
551
  const gitopsTool = gitopsToolForNode(node)
550
552
  const chips = buildChips(node)
551
553
  const dim = getNodeDimensions(node)
554
+ const KindIcon = getTopologyIcon(kind)
552
555
 
553
556
  return (
554
557
  <>
@@ -584,7 +587,7 @@ const GitOpsResourceNode = memo(function GitOpsResourceNode({ data }: NodeProps<
584
587
  >
585
588
  <div className="px-3 py-2.5">
586
589
  <div className="mb-1 flex items-center gap-1.5">
587
- <span className={`topology-icon topology-icon-${kind.toLowerCase()}`} />
590
+ <KindIcon className="h-3.5 w-3.5 shrink-0 text-theme-text-tertiary" aria-hidden />
588
591
  <span className="truncate text-[10px] font-medium uppercase tracking-wide text-theme-text-tertiary">
589
592
  {node.role === 'group' ? displayKind((node.data?.groupedKind as string) || kind) : displayKind(kind)}
590
593
  </span>