@skyhook-io/k8s-ui 1.14.6 → 1.14.7
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 +1 -1
- package/src/components/applications/ApplicationDetail.tsx +36 -4
- package/src/components/applications/ApplicationsView.tsx +2 -1
- package/src/components/audit/AuditAlerts.tsx +35 -37
- package/src/components/audit/AuditFindingsTable.tsx +23 -27
- package/src/components/charts/PrometheusChartsView.tsx +29 -10
- package/src/components/checks/ChecksView.tsx +56 -26
- package/src/components/gitops/GitOpsDetailLayout.tsx +12 -9
- package/src/components/issues/IssuesView.tsx +35 -54
- package/src/components/logs/LogCore.tsx +40 -9
- package/src/components/resources/ResourcesSidebar.tsx +66 -78
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +21 -15
- package/src/components/resources/renderers/CompositionRenderer.tsx +30 -10
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +16 -12
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/GenericRenderer.tsx +13 -7
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +6 -4
- package/src/components/resources/renderers/PolicyCoverageSection.tsx +7 -4
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +13 -15
- package/src/components/resources/renderers/SbomReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +13 -6
- package/src/components/shared/CreateResourceDialog.tsx +7 -4
- package/src/components/timeline/TimelineList.tsx +14 -11
- package/src/components/timeline/TimelineStrip.tsx +26 -4
- package/src/components/timeline/TimelineSwimlanes.tsx +3 -5
- package/src/components/timeline/TimelineToolbar.tsx +43 -10
- package/src/components/topology/TopologyControls.tsx +27 -8
- package/src/components/topology/TopologyGraph.tsx +14 -5
- package/src/components/trace/ReachabilityView.tsx +17 -16
- package/src/components/ui/CardSection.tsx +79 -35
- package/src/components/ui/Collapse.test.tsx +74 -0
- package/src/components/ui/Collapse.tsx +115 -29
- package/src/components/ui/DialogPortal.tsx +7 -2
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +7 -4
- package/src/components/ui/RestrictedState.tsx +7 -4
- package/src/components/ui/RowActionMenu.tsx +20 -8
- package/src/components/ui/SelectMenu.tsx +18 -6
- package/src/components/ui/YamlEditor.tsx +7 -9
- package/src/components/ui/drawer-components.tsx +14 -21
- package/src/components/ui/index.ts +30 -22
- package/src/components/workload/WorkloadView.tsx +6 -10
- package/src/hooks/useAnimatedUnmount.ts +21 -3
- package/src/theme/components.css +4 -1
- package/src/utils/animation.ts +93 -13
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useState, useMemo } from 'react'
|
|
2
|
-
import { ShieldCheck,
|
|
1
|
+
import { useState, useMemo, useId } from 'react'
|
|
2
|
+
import { ShieldCheck, CheckCircle2, XCircle } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
5
|
+
import { Collapse, CollapseChevron, useDisclosure, disclosurePanelId } from '../../ui/Collapse'
|
|
5
6
|
import { formatAge } from '../resource-utils'
|
|
6
7
|
import { SEVERITY_BADGE_COLORS, SEVERITY_ORDER, TrivyAlertBanner } from './trivy-shared'
|
|
7
8
|
import { BADGE_INACTIVE } from '../../../utils/badge-colors'
|
|
@@ -65,6 +66,10 @@ function groupChecks(checks: any[]): GroupedCheck[] {
|
|
|
65
66
|
export function ConfigAuditReportRenderer({ data }: ConfigAuditReportRendererProps) {
|
|
66
67
|
const [expandedSection, setExpandedSection] = useState(true)
|
|
67
68
|
const [expandedChecks, setExpandedChecks] = useState<Set<string>>(new Set())
|
|
69
|
+
const sectionDisclosure = useDisclosure(expandedSection)
|
|
70
|
+
// Check rows are mapped inline, so they can't each call useDisclosure; one
|
|
71
|
+
// generated prefix plus the check id keeps aria-controls unique.
|
|
72
|
+
const checkPanelBase = useId()
|
|
68
73
|
|
|
69
74
|
const report = data.report || {}
|
|
70
75
|
const summary = report.summary || {}
|
|
@@ -149,19 +154,22 @@ export function ConfigAuditReportRenderer({ data }: ConfigAuditReportRendererPro
|
|
|
149
154
|
{grouped.length > 0 && (
|
|
150
155
|
<Section title="Checks">
|
|
151
156
|
<button
|
|
157
|
+
{...sectionDisclosure.buttonProps}
|
|
152
158
|
onClick={() => setExpandedSection(!expandedSection)}
|
|
153
159
|
className="flex items-center gap-1 text-xs text-theme-text-secondary hover:text-theme-text-primary mb-2"
|
|
154
160
|
>
|
|
155
|
-
|
|
161
|
+
<CollapseChevron open={expandedSection} className="w-3.5 h-3.5" />
|
|
156
162
|
{grouped.length} unique checks{containerCount > 0 ? ` (${containerCount} containers scanned)` : ''}
|
|
157
163
|
</button>
|
|
158
|
-
{expandedSection
|
|
164
|
+
<Collapse open={expandedSection} unmountOnExit id={sectionDisclosure.panelId}>
|
|
159
165
|
<div className="space-y-0.5">
|
|
160
166
|
{grouped.map((group) => {
|
|
161
167
|
const isExpanded = expandedChecks.has(group.checkID)
|
|
162
168
|
return (
|
|
163
169
|
<div key={group.checkID}>
|
|
164
170
|
<button
|
|
171
|
+
aria-expanded={isExpanded}
|
|
172
|
+
aria-controls={disclosurePanelId(checkPanelBase, group.checkID)}
|
|
165
173
|
onClick={() => toggleCheck(group.checkID)}
|
|
166
174
|
className="w-full flex items-center gap-2 py-1.5 px-1 rounded hover:bg-theme-hover/50 text-left"
|
|
167
175
|
>
|
|
@@ -177,13 +185,9 @@ export function ConfigAuditReportRenderer({ data }: ConfigAuditReportRendererPro
|
|
|
177
185
|
{group.count > 1 && (
|
|
178
186
|
<span className="text-[10px] text-theme-text-tertiary shrink-0">{group.count} containers</span>
|
|
179
187
|
)}
|
|
180
|
-
{isExpanded
|
|
181
|
-
<ChevronDown className="w-3 h-3 text-theme-text-tertiary shrink-0" />
|
|
182
|
-
) : (
|
|
183
|
-
<ChevronRight className="w-3 h-3 text-theme-text-tertiary shrink-0" />
|
|
184
|
-
)}
|
|
188
|
+
<CollapseChevron open={isExpanded} className="w-3 h-3" />
|
|
185
189
|
</button>
|
|
186
|
-
{isExpanded
|
|
190
|
+
<Collapse open={isExpanded} mountLazily id={disclosurePanelId(checkPanelBase, group.checkID)}>
|
|
187
191
|
<div className="ml-6 mr-1 mb-2 p-2.5 bg-theme-elevated/50 rounded border border-theme-border/50 space-y-2">
|
|
188
192
|
<div className="text-[10px] font-mono text-theme-text-tertiary">{group.checkID}</div>
|
|
189
193
|
{group.description && (
|
|
@@ -209,12 +213,12 @@ export function ConfigAuditReportRenderer({ data }: ConfigAuditReportRendererPro
|
|
|
209
213
|
</div>
|
|
210
214
|
)}
|
|
211
215
|
</div>
|
|
212
|
-
|
|
216
|
+
</Collapse>
|
|
213
217
|
</div>
|
|
214
218
|
)
|
|
215
219
|
})}
|
|
216
220
|
</div>
|
|
217
|
-
|
|
221
|
+
</Collapse>
|
|
218
222
|
</Section>
|
|
219
223
|
)}
|
|
220
224
|
</>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import { KeyRound
|
|
2
|
+
import { KeyRound } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
5
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
5
6
|
import { formatAge } from '../resource-utils'
|
|
6
7
|
import { SEVERITY_BADGE_COLORS, TrivyAlertBanner, formatTrivyImage } from './trivy-shared'
|
|
7
8
|
import { pluralize } from '../../../utils/pluralize'
|
|
@@ -16,6 +17,7 @@ const INITIAL_SHOW_COUNT = 50
|
|
|
16
17
|
export function ExposedSecretReportRenderer({ data }: ExposedSecretReportRendererProps) {
|
|
17
18
|
const [showAll, setShowAll] = useState(false)
|
|
18
19
|
const [expanded, setExpanded] = useState(true)
|
|
20
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
19
21
|
|
|
20
22
|
const report = data.report || {}
|
|
21
23
|
const summary = report.summary || {}
|
|
@@ -70,13 +72,17 @@ export function ExposedSecretReportRenderer({ data }: ExposedSecretReportRendere
|
|
|
70
72
|
{secrets.length > 0 && (
|
|
71
73
|
<Section title="Exposed Secrets">
|
|
72
74
|
<button
|
|
75
|
+
{...buttonProps}
|
|
73
76
|
onClick={() => setExpanded(!expanded)}
|
|
74
77
|
className="flex items-center gap-1 text-xs text-theme-text-secondary hover:text-theme-text-primary mb-2"
|
|
75
78
|
>
|
|
76
|
-
|
|
79
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
77
80
|
{pluralize(secrets.length, 'secret')}
|
|
78
81
|
</button>
|
|
79
|
-
{
|
|
82
|
+
{/* The table can run to hundreds of rows; unmount it while closed
|
|
83
|
+
so a collapsed report stays cheap. Filters live above, so
|
|
84
|
+
nothing is lost. */}
|
|
85
|
+
<Collapse open={expanded} unmountOnExit id={panelId}>
|
|
80
86
|
<div className="overflow-x-auto -mx-1">
|
|
81
87
|
<table className="w-full text-xs">
|
|
82
88
|
<thead>
|
|
@@ -113,7 +119,7 @@ export function ExposedSecretReportRenderer({ data }: ExposedSecretReportRendere
|
|
|
113
119
|
</button>
|
|
114
120
|
)}
|
|
115
121
|
</div>
|
|
116
|
-
|
|
122
|
+
</Collapse>
|
|
117
123
|
</Section>
|
|
118
124
|
)}
|
|
119
125
|
</>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import { Info, CheckCircle, XCircle, AlertCircle,
|
|
2
|
+
import { Info, CheckCircle, XCircle, AlertCircle, FileCode, AlertTriangle, Layers } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
5
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
5
6
|
import { getGenericResourceStatus } from '../generic-status'
|
|
6
7
|
import type { HealthLevel } from '../resource-utils'
|
|
7
8
|
|
|
@@ -244,19 +245,23 @@ interface NestedObjectViewerProps {
|
|
|
244
245
|
|
|
245
246
|
function NestedObjectViewer({ name, value, depth = 0 }: NestedObjectViewerProps) {
|
|
246
247
|
const [expanded, setExpanded] = useState(depth < 1)
|
|
248
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
247
249
|
|
|
248
250
|
if (Array.isArray(value)) {
|
|
249
251
|
return (
|
|
250
252
|
<div className="text-sm">
|
|
251
253
|
<button
|
|
254
|
+
{...buttonProps}
|
|
252
255
|
onClick={() => setExpanded(!expanded)}
|
|
253
256
|
className="flex items-center gap-1 text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
254
257
|
>
|
|
255
|
-
|
|
258
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
256
259
|
<span className="font-medium">{name}</span>
|
|
257
260
|
<span className="text-xs text-theme-text-tertiary">({value.length} items)</span>
|
|
258
261
|
</button>
|
|
259
|
-
{
|
|
262
|
+
{/* Recursive and unbounded: closed branches unmount so a deep object
|
|
263
|
+
costs only the rows the user has actually opened. */}
|
|
264
|
+
<Collapse open={expanded} unmountOnExit id={panelId}>
|
|
260
265
|
<div className="ml-5 mt-1 space-y-1">
|
|
261
266
|
{value.map((item, i) => {
|
|
262
267
|
if (typeof item === 'object' && item !== null) {
|
|
@@ -269,7 +274,7 @@ function NestedObjectViewer({ name, value, depth = 0 }: NestedObjectViewerProps)
|
|
|
269
274
|
)
|
|
270
275
|
})}
|
|
271
276
|
</div>
|
|
272
|
-
|
|
277
|
+
</Collapse>
|
|
273
278
|
</div>
|
|
274
279
|
)
|
|
275
280
|
}
|
|
@@ -282,14 +287,15 @@ function NestedObjectViewer({ name, value, depth = 0 }: NestedObjectViewerProps)
|
|
|
282
287
|
return (
|
|
283
288
|
<div className="text-sm">
|
|
284
289
|
<button
|
|
290
|
+
{...buttonProps}
|
|
285
291
|
onClick={() => setExpanded(!expanded)}
|
|
286
292
|
className="flex items-center gap-1 text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
287
293
|
>
|
|
288
|
-
|
|
294
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
289
295
|
<span className="font-medium">{name}</span>
|
|
290
296
|
<span className="text-xs text-theme-text-tertiary">({entries.length} fields)</span>
|
|
291
297
|
</button>
|
|
292
|
-
{expanded
|
|
298
|
+
<Collapse open={expanded} unmountOnExit id={panelId}>
|
|
293
299
|
<div className="ml-5 mt-1 space-y-1">
|
|
294
300
|
{simpleEntries.map(([k, v]) => (
|
|
295
301
|
<div key={k} className="flex items-start gap-2 text-xs">
|
|
@@ -301,7 +307,7 @@ function NestedObjectViewer({ name, value, depth = 0 }: NestedObjectViewerProps)
|
|
|
301
307
|
<NestedObjectViewer key={k} name={formatFieldName(k)} value={v} depth={depth + 1} />
|
|
302
308
|
))}
|
|
303
309
|
</div>
|
|
304
|
-
|
|
310
|
+
</Collapse>
|
|
305
311
|
</div>
|
|
306
312
|
)
|
|
307
313
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type React from 'react'
|
|
2
|
-
import { Shield, ShieldCheck, ShieldAlert, FileWarning, ListChecks
|
|
2
|
+
import { Shield, ShieldCheck, ShieldAlert, FileWarning, ListChecks } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { useState } from 'react'
|
|
5
5
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
6
|
+
import { Collapse, CollapseChevron } from '../../ui/Collapse'
|
|
6
7
|
import {
|
|
7
8
|
getPolicyReportSummary,
|
|
8
9
|
getPolicyReportResults,
|
|
@@ -60,7 +61,7 @@ function ResultRow({ result }: { result: any }) {
|
|
|
60
61
|
onClick={() => hasMessage && setExpanded(!expanded)}
|
|
61
62
|
>
|
|
62
63
|
{hasMessage ? (
|
|
63
|
-
|
|
64
|
+
<CollapseChevron open={expanded} className="w-3 h-3" />
|
|
64
65
|
) : (
|
|
65
66
|
<span className="w-3 shrink-0" />
|
|
66
67
|
)}
|
|
@@ -80,7 +81,8 @@ function ResultRow({ result }: { result: any }) {
|
|
|
80
81
|
<span className="text-theme-text-tertiary truncate">/ {result.rule}</span>
|
|
81
82
|
)}
|
|
82
83
|
</div>
|
|
83
|
-
{
|
|
84
|
+
{/* A report can hold hundreds of rows; each detail renders on first open. */}
|
|
85
|
+
<Collapse open={expanded && hasMessage} mountLazily>
|
|
84
86
|
<div className="px-2 pb-2 pl-7">
|
|
85
87
|
<div className="text-xs text-theme-text-secondary break-all card-inner">
|
|
86
88
|
{message}
|
|
@@ -97,7 +99,7 @@ function ResultRow({ result }: { result: any }) {
|
|
|
97
99
|
</div>
|
|
98
100
|
)}
|
|
99
101
|
</div>
|
|
100
|
-
|
|
102
|
+
</Collapse>
|
|
101
103
|
</div>
|
|
102
104
|
)
|
|
103
105
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ShieldAlert, ShieldCheck, ShieldQuestion } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { useState } from 'react'
|
|
4
|
-
import { ChevronDown, ChevronRight } from 'lucide-react'
|
|
5
4
|
import { Section } from '../../ui/drawer-components'
|
|
5
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
6
6
|
import { SEVERITY_BADGE } from '../../../utils/badge-colors'
|
|
7
7
|
import { isForbiddenError } from '../../../types/fetch-error'
|
|
8
8
|
import { LookupFailureNote } from './LookupFailureNote'
|
|
@@ -829,6 +829,7 @@ function PassingGroup({
|
|
|
829
829
|
}) {
|
|
830
830
|
const [open, setOpen] = useState(false)
|
|
831
831
|
const [showAll, setShowAll] = useState(false)
|
|
832
|
+
const { panelId, buttonProps } = useDisclosure(open)
|
|
832
833
|
|
|
833
834
|
// Nothing to open: the count is real but the subjects did not survive the
|
|
834
835
|
// server cap, so the label stands alone rather than offering an empty drawer.
|
|
@@ -843,14 +844,16 @@ function PassingGroup({
|
|
|
843
844
|
return (
|
|
844
845
|
<div className="pt-0.5">
|
|
845
846
|
<button
|
|
847
|
+
{...buttonProps}
|
|
846
848
|
type="button"
|
|
847
849
|
onClick={() => setOpen(!open)}
|
|
848
850
|
className="flex items-center gap-1 text-xs text-theme-text-tertiary hover:text-theme-text-secondary"
|
|
849
851
|
>
|
|
850
|
-
|
|
852
|
+
<CollapseChevron open={open} className="w-3 h-3" />
|
|
851
853
|
{label}
|
|
852
854
|
</button>
|
|
853
|
-
{
|
|
855
|
+
{/* One of these per rule; the subject list only renders once opened. */}
|
|
856
|
+
<Collapse open={open} mountLazily id={panelId}>
|
|
854
857
|
<div className="space-y-1.5 mt-1.5">
|
|
855
858
|
{visible.map((s, i) => (
|
|
856
859
|
<SubjectRow
|
|
@@ -891,7 +894,7 @@ function PassingGroup({
|
|
|
891
894
|
</div>
|
|
892
895
|
)}
|
|
893
896
|
</div>
|
|
894
|
-
|
|
897
|
+
</Collapse>
|
|
895
898
|
</div>
|
|
896
899
|
)
|
|
897
900
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useState, useMemo } from 'react'
|
|
2
|
-
import { Bell, Search
|
|
2
|
+
import { Bell, Search } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
|
|
5
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
5
6
|
import { Input } from '../../ui/Input'
|
|
6
7
|
import { BADGE_SEVERITY_COLORS } from '../../ui/Badge'
|
|
7
8
|
import {
|
|
@@ -116,6 +117,7 @@ function RuleGroupSection({ group, searchTerm }: { group: PrometheusRuleGroup; s
|
|
|
116
117
|
|
|
117
118
|
// Auto-expand when searching so matched rules are visible
|
|
118
119
|
const expanded = searchTerm ? filteredRules.length > 0 : manualExpanded
|
|
120
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
119
121
|
|
|
120
122
|
// If searching and no matches, hide the group entirely
|
|
121
123
|
if (searchTerm && filteredRules.length === 0) return null
|
|
@@ -123,11 +125,12 @@ function RuleGroupSection({ group, searchTerm }: { group: PrometheusRuleGroup; s
|
|
|
123
125
|
return (
|
|
124
126
|
<div className="card-inner">
|
|
125
127
|
<button
|
|
128
|
+
{...buttonProps}
|
|
126
129
|
onClick={() => setManualExpanded(!manualExpanded)}
|
|
127
130
|
className="flex items-center justify-between w-full text-left"
|
|
128
131
|
>
|
|
129
132
|
<div className="flex items-center gap-2">
|
|
130
|
-
<
|
|
133
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
131
134
|
<span className="text-sm text-theme-text-primary font-medium">{group.name}</span>
|
|
132
135
|
</div>
|
|
133
136
|
<div className="flex items-center gap-2">
|
|
@@ -147,20 +150,15 @@ function RuleGroupSection({ group, searchTerm }: { group: PrometheusRuleGroup; s
|
|
|
147
150
|
{group.recordCount > 0 && <span>{group.recordCount} recording</span>}
|
|
148
151
|
</div>
|
|
149
152
|
)}
|
|
150
|
-
<
|
|
151
|
-
className="
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
rule.type === 'alert'
|
|
158
|
-
? <AlertRuleCard key={`alert-${rule.alert}-${i}`} rule={rule} />
|
|
159
|
-
: <RecordingRuleCard key={`rec-${rule.record}-${i}`} rule={rule} />
|
|
160
|
-
))}
|
|
161
|
-
</div>
|
|
153
|
+
<Collapse open={expanded} id={panelId}>
|
|
154
|
+
<div className="mt-2 space-y-2">
|
|
155
|
+
{filteredRules.map((rule, i) => (
|
|
156
|
+
rule.type === 'alert'
|
|
157
|
+
? <AlertRuleCard key={`alert-${rule.alert}-${i}`} rule={rule} />
|
|
158
|
+
: <RecordingRuleCard key={`rec-${rule.record}-${i}`} rule={rule} />
|
|
159
|
+
))}
|
|
162
160
|
</div>
|
|
163
|
-
</
|
|
161
|
+
</Collapse>
|
|
164
162
|
</div>
|
|
165
163
|
)
|
|
166
164
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useMemo } from 'react'
|
|
2
|
-
import { Package,
|
|
2
|
+
import { Package, Search } from 'lucide-react'
|
|
3
3
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
4
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
4
5
|
import { Input } from '../../ui/Input'
|
|
5
6
|
import { formatAge } from '../resource-utils'
|
|
6
7
|
import { formatTrivyImage } from './trivy-shared'
|
|
@@ -14,6 +15,7 @@ const INITIAL_SHOW_COUNT = 100
|
|
|
14
15
|
export function SbomReportRenderer({ data }: SbomReportRendererProps) {
|
|
15
16
|
const [showAll, setShowAll] = useState(false)
|
|
16
17
|
const [expanded, setExpanded] = useState(true)
|
|
18
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
17
19
|
const [searchTerm, setSearchTerm] = useState('')
|
|
18
20
|
|
|
19
21
|
const report = data.report || {}
|
|
@@ -63,13 +65,17 @@ export function SbomReportRenderer({ data }: SbomReportRendererProps) {
|
|
|
63
65
|
{bom.length > 0 && (
|
|
64
66
|
<Section title="Components">
|
|
65
67
|
<button
|
|
68
|
+
{...buttonProps}
|
|
66
69
|
onClick={() => setExpanded(!expanded)}
|
|
67
70
|
className="flex items-center gap-1 text-xs text-theme-text-secondary hover:text-theme-text-primary mb-2"
|
|
68
71
|
>
|
|
69
|
-
|
|
72
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
70
73
|
{bom.length} components
|
|
71
74
|
</button>
|
|
72
|
-
{
|
|
75
|
+
{/* The table can run to hundreds of rows; unmount it while closed
|
|
76
|
+
so a collapsed report stays cheap. Filters live above, so
|
|
77
|
+
nothing is lost. */}
|
|
78
|
+
<Collapse open={expanded} unmountOnExit id={panelId}>
|
|
73
79
|
<div className="overflow-x-auto -mx-1">
|
|
74
80
|
{/* Search */}
|
|
75
81
|
<div className="flex items-center gap-2 mb-2 px-1">
|
|
@@ -131,7 +137,7 @@ export function SbomReportRenderer({ data }: SbomReportRendererProps) {
|
|
|
131
137
|
</button>
|
|
132
138
|
)}
|
|
133
139
|
</div>
|
|
134
|
-
|
|
140
|
+
</Collapse>
|
|
135
141
|
</Section>
|
|
136
142
|
)}
|
|
137
143
|
</>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useState, useMemo } from 'react'
|
|
2
|
-
import { Shield, Package, ChevronDown,
|
|
2
|
+
import { Shield, Package, ChevronDown, ChevronUp, ExternalLink, Search, ArrowUpDown, Wrench } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
5
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
5
6
|
import { Input } from '../../ui/Input'
|
|
6
7
|
import { formatAge } from '../resource-utils'
|
|
7
8
|
import { SEVERITY_BADGE_COLORS, SEVERITY_BAR_COLORS, SEVERITY_TEXT_COLORS, SEVERITY_ORDER, TrivyAlertBanner, formatTrivyImage } from './trivy-shared'
|
|
@@ -27,6 +28,7 @@ const COLUMNS: { key: SortColumn; label: string }[] = [
|
|
|
27
28
|
export function VulnerabilityReportRenderer({ data }: VulnerabilityReportRendererProps) {
|
|
28
29
|
const [showAll, setShowAll] = useState(false)
|
|
29
30
|
const [expanded, setExpanded] = useState(true)
|
|
31
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
30
32
|
const [searchTerm, setSearchTerm] = useState('')
|
|
31
33
|
const [severityFilters, setSeverityFilters] = useState<Set<string>>(new Set())
|
|
32
34
|
const [fixableOnly, setFixableOnly] = useState(false)
|
|
@@ -211,13 +213,17 @@ export function VulnerabilityReportRenderer({ data }: VulnerabilityReportRendere
|
|
|
211
213
|
{vulnerabilities.length > 0 && (
|
|
212
214
|
<Section title="Vulnerabilities" icon={Package}>
|
|
213
215
|
<button
|
|
216
|
+
{...buttonProps}
|
|
214
217
|
onClick={() => setExpanded(!expanded)}
|
|
215
218
|
className="flex items-center gap-1 text-xs text-theme-text-secondary hover:text-theme-text-primary mb-2"
|
|
216
219
|
>
|
|
217
|
-
|
|
220
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
218
221
|
{vulnerabilities.length} vulnerabilities
|
|
219
222
|
</button>
|
|
220
|
-
{
|
|
223
|
+
{/* The table can run to hundreds of rows; unmount it while closed
|
|
224
|
+
so a collapsed report stays cheap. Filters live above, so
|
|
225
|
+
nothing is lost. */}
|
|
226
|
+
<Collapse open={expanded} unmountOnExit id={panelId}>
|
|
221
227
|
<div className="overflow-x-auto -mx-1">
|
|
222
228
|
{/* Search and filter toolbar */}
|
|
223
229
|
<div className="flex items-center gap-2 mb-2 px-1">
|
|
@@ -339,7 +345,7 @@ export function VulnerabilityReportRenderer({ data }: VulnerabilityReportRendere
|
|
|
339
345
|
</button>
|
|
340
346
|
)}
|
|
341
347
|
</div>
|
|
342
|
-
|
|
348
|
+
</Collapse>
|
|
343
349
|
</Section>
|
|
344
350
|
)}
|
|
345
351
|
</>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import {
|
|
1
|
+
import { useState, useId } from 'react'
|
|
2
|
+
import { Box } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { formatAge } from '../../resource-utils'
|
|
5
|
+
import { Collapse, CollapseChevron, disclosurePanelId } from '../../../ui/Collapse'
|
|
5
6
|
import { revisionRoleBadges } from '../../../shared/ResourceActionsBar'
|
|
6
7
|
import type { WorkloadRevision, WorkloadPodInfo } from '../../../../types'
|
|
7
8
|
|
|
@@ -38,6 +39,9 @@ function podToneClass(pod: WorkloadPodInfo): string {
|
|
|
38
39
|
// to that dialog — this is purely informational.
|
|
39
40
|
export function ReplicaSetProgression({ revisions, pods, namespace, onNavigate }: ReplicaSetProgressionProps) {
|
|
40
41
|
const [expanded, setExpanded] = useState<Set<number>>(new Set())
|
|
42
|
+
// Revision rows are mapped inline, so they can't each call useDisclosure;
|
|
43
|
+
// one generated prefix plus the revision number keeps aria-controls unique.
|
|
44
|
+
const panelBase = useId()
|
|
41
45
|
|
|
42
46
|
const podsByHash = new Map<string, WorkloadPodInfo[]>()
|
|
43
47
|
for (const pod of pods ?? []) {
|
|
@@ -59,17 +63,20 @@ export function ReplicaSetProgression({ revisions, pods, namespace, onNavigate }
|
|
|
59
63
|
<div className="space-y-1">
|
|
60
64
|
{revisions.map((rev) => {
|
|
61
65
|
const revPods = rev.podHash ? podsByHash.get(rev.podHash) ?? [] : []
|
|
62
|
-
|
|
66
|
+
// One predicate for header, caret and panel: no pods, nothing to open.
|
|
67
|
+
const isOpen = expanded.has(rev.number) && revPods.length > 0
|
|
63
68
|
return (
|
|
64
69
|
<div key={rev.number} className="rounded border border-theme-border/50">
|
|
65
70
|
<button
|
|
66
71
|
type="button"
|
|
72
|
+
aria-expanded={isOpen}
|
|
73
|
+
aria-controls={disclosurePanelId(panelBase, rev.number)}
|
|
67
74
|
onClick={() => toggle(rev.number)}
|
|
68
75
|
className="flex w-full items-center gap-2 px-2 py-1.5 text-left hover:bg-theme-hover"
|
|
69
76
|
disabled={revPods.length === 0}
|
|
70
77
|
>
|
|
71
78
|
{revPods.length > 0 ? (
|
|
72
|
-
|
|
79
|
+
<CollapseChevron open={isOpen} className="h-3.5 w-3.5" />
|
|
73
80
|
) : (
|
|
74
81
|
<span className="w-3.5 shrink-0" />
|
|
75
82
|
)}
|
|
@@ -85,7 +92,7 @@ export function ReplicaSetProgression({ revisions, pods, namespace, onNavigate }
|
|
|
85
92
|
<span className="shrink-0 text-xs tabular-nums text-theme-text-tertiary">{rev.replicas ?? 0} replicas</span>
|
|
86
93
|
<span className="shrink-0 text-xs text-theme-text-tertiary">{formatAge(rev.createdAt)}</span>
|
|
87
94
|
</button>
|
|
88
|
-
{isOpen
|
|
95
|
+
<Collapse open={isOpen} mountLazily id={disclosurePanelId(panelBase, rev.number)}>
|
|
89
96
|
<div className="space-y-0.5 border-t border-theme-border/50 px-2 py-1.5 pl-8">
|
|
90
97
|
{revPods.map((pod) => (
|
|
91
98
|
<div key={pod.name} className="flex items-center gap-2 py-0.5 text-xs">
|
|
@@ -107,7 +114,7 @@ export function ReplicaSetProgression({ revisions, pods, namespace, onNavigate }
|
|
|
107
114
|
</div>
|
|
108
115
|
))}
|
|
109
116
|
</div>
|
|
110
|
-
|
|
117
|
+
</Collapse>
|
|
111
118
|
</div>
|
|
112
119
|
)
|
|
113
120
|
})}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState, type DragEvent as ReactDragEvent } from 'react'
|
|
2
|
-
import { AlertTriangle, Check,
|
|
2
|
+
import { AlertTriangle, Check, Loader2, Upload, X } from 'lucide-react'
|
|
3
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../ui/Collapse'
|
|
3
4
|
import { ConfirmDialog } from '../ui/ConfirmDialog'
|
|
4
5
|
import { DialogPortal } from '../ui/DialogPortal'
|
|
5
6
|
import { YamlEditor, type YamlSchemaLoader } from '../ui/YamlEditor'
|
|
@@ -588,6 +589,7 @@ export function CreateResourceDialog({
|
|
|
588
589
|
|
|
589
590
|
function ApplyErrorBanner({ error }: { error: string }) {
|
|
590
591
|
const [expanded, setExpanded] = useState(false)
|
|
592
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
591
593
|
const parsed = formatApplyError(error)
|
|
592
594
|
const hasFriendly = Boolean(parsed.suggestion)
|
|
593
595
|
return (
|
|
@@ -603,19 +605,20 @@ function ApplyErrorBanner({ error }: { error: string }) {
|
|
|
603
605
|
</div>
|
|
604
606
|
{hasFriendly && (
|
|
605
607
|
<button
|
|
608
|
+
{...buttonProps}
|
|
606
609
|
type="button"
|
|
607
610
|
onClick={() => setExpanded((value) => !value)}
|
|
608
611
|
className="flex items-center gap-1 px-3 pb-2 text-red-500/60 hover:text-red-500/80 dark:text-red-400/60 dark:hover:text-red-400/80"
|
|
609
612
|
>
|
|
610
|
-
|
|
613
|
+
<CollapseChevron open={expanded} inheritColor className="h-3 w-3" />
|
|
611
614
|
Details
|
|
612
615
|
</button>
|
|
613
616
|
)}
|
|
614
|
-
{expanded && hasFriendly
|
|
617
|
+
<Collapse open={expanded && hasFriendly} id={panelId}>
|
|
615
618
|
<div className="break-all px-3 pb-2 font-mono leading-relaxed text-red-500/60 dark:text-red-400/60">
|
|
616
619
|
{parsed.raw}
|
|
617
620
|
</div>
|
|
618
|
-
|
|
621
|
+
</Collapse>
|
|
619
622
|
</div>
|
|
620
623
|
)
|
|
621
624
|
}
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
CheckCircle,
|
|
8
8
|
Clock,
|
|
9
9
|
RefreshCw,
|
|
10
|
-
ChevronRight,
|
|
11
10
|
Plus,
|
|
12
11
|
Trash2,
|
|
13
12
|
Shield,
|
|
14
13
|
X,
|
|
15
14
|
} from 'lucide-react'
|
|
16
15
|
import { clsx } from 'clsx'
|
|
16
|
+
import { Collapse, CollapseChevron } from '../ui/Collapse'
|
|
17
17
|
import { DiffViewer, DiffBadge } from './DiffViewer'
|
|
18
18
|
import { TimelineToolbar } from './TimelineToolbar'
|
|
19
19
|
import {
|
|
@@ -696,17 +696,20 @@ function ActivityCard({ item, expanded, onToggle, onResourceClick, compact, sele
|
|
|
696
696
|
aria-label={expanded ? 'Collapse changes' : 'Expand changes'}
|
|
697
697
|
className="shrink-0 rounded p-0.5 text-theme-text-disabled hover:bg-theme-elevated hover:text-theme-text-secondary"
|
|
698
698
|
>
|
|
699
|
-
<
|
|
699
|
+
<CollapseChevron open={expanded} inheritColor className="w-4 h-4" />
|
|
700
700
|
</button>
|
|
701
701
|
)}
|
|
702
702
|
</div>
|
|
703
703
|
|
|
704
|
-
{/* Expanded details - only for items with diffs
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
<
|
|
709
|
-
|
|
704
|
+
{/* Expanded details - only for items with diffs. A list holds many
|
|
705
|
+
cards, each diff is real work to render: mount on first open. */}
|
|
706
|
+
{hasExpandableContent && item.diff && (
|
|
707
|
+
<Collapse open={expanded} mountLazily>
|
|
708
|
+
<div className="mt-3 pt-3 border-t-subtle">
|
|
709
|
+
<div className="text-xs text-theme-text-tertiary mb-2">Changes:</div>
|
|
710
|
+
<DiffViewer diff={item.diff} />
|
|
711
|
+
</div>
|
|
712
|
+
</Collapse>
|
|
710
713
|
)}
|
|
711
714
|
</div>
|
|
712
715
|
</div>
|
|
@@ -826,12 +829,12 @@ function AggregatedActivityCard({ first, last, count, reason, expanded, onToggle
|
|
|
826
829
|
aria-label={expanded ? 'Collapse occurrences' : 'Expand occurrences'}
|
|
827
830
|
className="shrink-0 rounded p-0.5 text-theme-text-disabled hover:bg-theme-elevated hover:text-theme-text-secondary"
|
|
828
831
|
>
|
|
829
|
-
<
|
|
832
|
+
<CollapseChevron open={expanded} inheritColor className="w-4 h-4" />
|
|
830
833
|
</button>
|
|
831
834
|
</div>
|
|
832
835
|
|
|
833
836
|
{/* Expanded details */}
|
|
834
|
-
{expanded
|
|
837
|
+
<Collapse open={expanded}>
|
|
835
838
|
<div className="mt-3 pt-3 border-t-subtle space-y-3">
|
|
836
839
|
{/* First occurrence */}
|
|
837
840
|
<div className="flex items-start gap-2">
|
|
@@ -865,7 +868,7 @@ function AggregatedActivityCard({ first, last, count, reason, expanded, onToggle
|
|
|
865
868
|
</div>
|
|
866
869
|
</div>
|
|
867
870
|
</div>
|
|
868
|
-
|
|
871
|
+
</Collapse>
|
|
869
872
|
</div>
|
|
870
873
|
</div>
|
|
871
874
|
)
|