@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
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
2
|
import { SEVERITY_DOT } from '../../../utils/badge-colors'
|
|
3
|
-
import {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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>
|