@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,7 @@
|
|
|
1
|
-
import { useRef } from 'react'
|
|
1
|
+
import { useRef, type RefObject } from 'react'
|
|
2
2
|
import { Search, X } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
|
+
import { Input } from './Input'
|
|
4
5
|
import { useRegisterShortcut, type ShortcutScope } from '../../hooks/useKeyboardShortcuts'
|
|
5
6
|
|
|
6
7
|
/** The standard list-view search box: themed input with a `/`-to-focus
|
|
@@ -17,6 +18,9 @@ export function SearchBox({
|
|
|
17
18
|
className,
|
|
18
19
|
onEnter,
|
|
19
20
|
onArrowDown,
|
|
21
|
+
inputRef: externalRef,
|
|
22
|
+
shortcutEnabled = true,
|
|
23
|
+
onBlur,
|
|
20
24
|
}: {
|
|
21
25
|
value: string
|
|
22
26
|
onChange: (value: string) => void
|
|
@@ -31,8 +35,17 @@ export function SearchBox({
|
|
|
31
35
|
onEnter?: () => void
|
|
32
36
|
/** ArrowDown in the box — hand focus off to list keyboard navigation. */
|
|
33
37
|
onArrowDown?: () => void
|
|
38
|
+
/** Externally-owned input ref — lets a collapsible wrapper focus the box
|
|
39
|
+
* after it mounts. Falls back to an internal ref when omitted. */
|
|
40
|
+
inputRef?: RefObject<HTMLInputElement | null>
|
|
41
|
+
/** Set false when a wrapper owns the `/` shortcut, so it isn't registered
|
|
42
|
+
* twice under the same id. */
|
|
43
|
+
shortcutEnabled?: boolean
|
|
44
|
+
/** Blur handler — a collapsible wrapper uses it to fold back when empty. */
|
|
45
|
+
onBlur?: () => void
|
|
34
46
|
}) {
|
|
35
|
-
const
|
|
47
|
+
const internalRef = useRef<HTMLInputElement>(null)
|
|
48
|
+
const inputRef = externalRef ?? internalRef
|
|
36
49
|
|
|
37
50
|
useRegisterShortcut({
|
|
38
51
|
id: shortcutId,
|
|
@@ -40,18 +53,19 @@ export function SearchBox({
|
|
|
40
53
|
description: 'Focus search',
|
|
41
54
|
category: 'Search',
|
|
42
55
|
scope,
|
|
56
|
+
enabled: shortcutEnabled,
|
|
43
57
|
handler: () => inputRef.current?.focus(),
|
|
44
58
|
})
|
|
45
59
|
|
|
46
60
|
return (
|
|
47
61
|
<div className={clsx('relative', className)}>
|
|
48
62
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-theme-text-tertiary" />
|
|
49
|
-
<
|
|
63
|
+
<Input
|
|
50
64
|
ref={inputRef}
|
|
51
|
-
type="text"
|
|
52
65
|
value={value}
|
|
53
66
|
placeholder={placeholder}
|
|
54
67
|
onChange={(e) => onChange(e.target.value)}
|
|
68
|
+
onBlur={onBlur}
|
|
55
69
|
onKeyDown={(e) => {
|
|
56
70
|
if (e.key === 'Escape') {
|
|
57
71
|
inputRef.current?.blur()
|
|
@@ -2,6 +2,7 @@ import { useRef, useState, useMemo, useEffect, useCallback } from 'react'
|
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
3
|
import { X } from 'lucide-react'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
5
|
+
import { Input } from './Input'
|
|
5
6
|
|
|
6
7
|
// A structured search input: free-text plus removable `key:value` modifier
|
|
7
8
|
// PILLS (ns:, kind:, label:, …). Typing a complete modifier token and pressing
|
|
@@ -236,9 +237,8 @@ export function SearchPillInput({
|
|
|
236
237
|
less
|
|
237
238
|
</button>
|
|
238
239
|
)}
|
|
239
|
-
<
|
|
240
|
+
<Input
|
|
240
241
|
ref={inputRef}
|
|
241
|
-
type="text"
|
|
242
242
|
value={text}
|
|
243
243
|
onChange={(e) => { setDismissed(false); onChange({ text: e.target.value, pills }) }}
|
|
244
244
|
onKeyDown={handleKeyDown}
|
|
@@ -215,7 +215,7 @@ function ToastItem({ toast, onDismiss }: { toast: Toast; onDismiss: () => void }
|
|
|
215
215
|
<button
|
|
216
216
|
onClick={toast.onDetailClick}
|
|
217
217
|
className={clsx(
|
|
218
|
-
'mt-1.5 block text-xs font-mono break-
|
|
218
|
+
'mt-1.5 block text-xs font-mono break-words text-left rounded px-1.5 py-1 -ml-1.5 transition-colors',
|
|
219
219
|
isError
|
|
220
220
|
? 'text-red-300 hover:bg-red-900/50'
|
|
221
221
|
: isSuccess
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createContext, useContext, useState } from 'react'
|
|
2
2
|
import { ChevronRight, Copy, Check, Tag, AlertTriangle, CheckCircle, ExternalLink, Layers, X, Minus } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { formatAge, formatDuration, formatResources } from '../resources/resource-utils'
|
|
@@ -532,9 +532,11 @@ function ContainerResourcesLine({ resources }: { resources: any }) {
|
|
|
532
532
|
const effective = getEffectiveResources(resources)
|
|
533
533
|
if (Object.keys(effective).length === 0) return null
|
|
534
534
|
return (
|
|
535
|
-
<
|
|
536
|
-
|
|
537
|
-
|
|
535
|
+
<Tooltip content="Effective requests (requests, falling back to limits)" delay={150}>
|
|
536
|
+
<div className="text-xs text-theme-text-tertiary mt-1">
|
|
537
|
+
Resources: {formatResources(effective)}
|
|
538
|
+
</div>
|
|
539
|
+
</Tooltip>
|
|
538
540
|
)
|
|
539
541
|
}
|
|
540
542
|
|
|
@@ -551,11 +553,15 @@ export function PodTemplateSection({ template }: { template: any }) {
|
|
|
551
553
|
{initContainers.map((c: any) => (
|
|
552
554
|
<div key={c.name} className="card-inner text-sm border-l-2 border-yellow-500/40">
|
|
553
555
|
<div className="font-medium text-theme-text-primary">{c.name}</div>
|
|
554
|
-
<
|
|
556
|
+
<Tooltip content={c.image} delay={300} wrapperClassName="block min-w-0">
|
|
557
|
+
<div className="text-xs text-theme-text-secondary truncate">{c.image}</div>
|
|
558
|
+
</Tooltip>
|
|
555
559
|
{(c.command || c.args) && (
|
|
556
|
-
<
|
|
557
|
-
|
|
558
|
-
|
|
560
|
+
<Tooltip content={[...(c.command || []), ...(c.args || [])].join(' ')} delay={300} wrapperClassName="block min-w-0">
|
|
561
|
+
<div className="text-xs text-theme-text-tertiary font-mono mt-1 truncate">
|
|
562
|
+
$ {[...(c.command || []), ...(c.args || [])].join(' ')}
|
|
563
|
+
</div>
|
|
564
|
+
</Tooltip>
|
|
559
565
|
)}
|
|
560
566
|
<ContainerResourcesLine resources={c.resources} />
|
|
561
567
|
</div>
|
|
@@ -566,7 +572,9 @@ export function PodTemplateSection({ template }: { template: any }) {
|
|
|
566
572
|
{containers.map((c: any) => (
|
|
567
573
|
<div key={c.name} className="card-inner text-sm">
|
|
568
574
|
<div className="font-medium text-theme-text-primary">{c.name}</div>
|
|
569
|
-
<
|
|
575
|
+
<Tooltip content={c.image} delay={300} wrapperClassName="block min-w-0">
|
|
576
|
+
<div className="text-xs text-theme-text-secondary truncate">{c.image}</div>
|
|
577
|
+
</Tooltip>
|
|
570
578
|
{c.ports && (
|
|
571
579
|
<div className="text-xs text-theme-text-tertiary mt-1">
|
|
572
580
|
Ports: {c.ports.map((p: any) => `${p.name ? `${p.name}: ` : ''}${p.containerPort}/${p.protocol || 'TCP'}`).join(', ')}
|
|
@@ -786,6 +794,7 @@ export function RelatedResourcesSection({ relationships, onNavigate }: RelatedRe
|
|
|
786
794
|
(relationships.configRefs && relationships.configRefs.length > 0) ||
|
|
787
795
|
(relationships.consumers && relationships.consumers.length > 0) ||
|
|
788
796
|
(relationships.scalers && relationships.scalers.length > 0) ||
|
|
797
|
+
(relationships.storageRefs && relationships.storageRefs.length > 0) ||
|
|
789
798
|
(relationships.pdbs && relationships.pdbs.length > 0) ||
|
|
790
799
|
(relationships.networkPolicies && relationships.networkPolicies.length > 0) ||
|
|
791
800
|
(relationships.resourceClaims && relationships.resourceClaims.length > 0) ||
|
|
@@ -829,6 +838,9 @@ export function RelatedResourcesSection({ relationships, onNavigate }: RelatedRe
|
|
|
829
838
|
{relationships.scalers && relationships.scalers.length > 0 && (
|
|
830
839
|
<RelationshipGroup label="Autoscaler" refs={dedupeRefs(relationships.scalers)} onNavigate={onNavigate} />
|
|
831
840
|
)}
|
|
841
|
+
{relationships.storageRefs && relationships.storageRefs.length > 0 && (
|
|
842
|
+
<RelationshipGroup label="Storage" refs={dedupeRefs(relationships.storageRefs)} onNavigate={onNavigate} />
|
|
843
|
+
)}
|
|
832
844
|
{relationships.pdbs && relationships.pdbs.length > 0 && (
|
|
833
845
|
<RelationshipGroup label="Disruption Budget" refs={dedupeRefs(relationships.pdbs)} onNavigate={onNavigate} />
|
|
834
846
|
)}
|
|
@@ -905,30 +917,31 @@ export function ResourceRefBadge({ resourceRef, onClick, wrapAtSeparator }: Reso
|
|
|
905
917
|
const layoutClass = wrapAtSeparator
|
|
906
918
|
? 'badge max-w-full min-w-0 flex-wrap items-center whitespace-normal text-left leading-tight'
|
|
907
919
|
: 'badge'
|
|
920
|
+
const tooltip = `${resourceRef.kind}: ${resourceRef.namespace}/${resourceRef.name}`
|
|
908
921
|
|
|
909
922
|
if (onClick) {
|
|
910
923
|
return (
|
|
911
|
-
<
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
924
|
+
<Tooltip content={tooltip} delay={150}>
|
|
925
|
+
<button
|
|
926
|
+
onClick={() => onClick(resourceRef)}
|
|
927
|
+
className={clsx(
|
|
928
|
+
layoutClass,
|
|
929
|
+
'hover:brightness-[0.92] dark:hover:brightness-125 transition-[filter]',
|
|
930
|
+
kindClass
|
|
931
|
+
)}
|
|
932
|
+
>
|
|
933
|
+
{content}
|
|
934
|
+
</button>
|
|
935
|
+
</Tooltip>
|
|
922
936
|
)
|
|
923
937
|
}
|
|
924
938
|
|
|
925
939
|
return (
|
|
926
|
-
<
|
|
927
|
-
className={clsx(layoutClass, kindClass)}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
</span>
|
|
940
|
+
<Tooltip content={tooltip} delay={300}>
|
|
941
|
+
<span className={clsx(layoutClass, kindClass)}>
|
|
942
|
+
{content}
|
|
943
|
+
</span>
|
|
944
|
+
</Tooltip>
|
|
932
945
|
)
|
|
933
946
|
}
|
|
934
947
|
|
|
@@ -10,6 +10,7 @@ export type { EmptyStateTone, EmptyStateVariant } from './EmptyState'
|
|
|
10
10
|
export { RestrictedState } from './RestrictedState'
|
|
11
11
|
export { FetchResult } from './FetchResult'
|
|
12
12
|
export { SearchBox } from './SearchBox'
|
|
13
|
+
export { Input } from './Input'
|
|
13
14
|
export { FilterPill } from './FilterPill'
|
|
14
15
|
export type { FilterPillTone } from './FilterPill'
|
|
15
16
|
export { StatusDot, mapHealthToTone } from './status-tone'
|
|
@@ -19,6 +20,7 @@ export { ConfirmDialog } from './ConfirmDialog'
|
|
|
19
20
|
export { HealthRing } from './HealthRing'
|
|
20
21
|
export { MetricsChart, MetricsSparkline } from './MetricsChart'
|
|
21
22
|
export * from './drawer-components'
|
|
23
|
+
export { Collapse, CollapseChevron } from './Collapse'
|
|
22
24
|
export { ResourceBar } from './ResourceBar'
|
|
23
25
|
export { ForceDeleteConfirmDialog } from './ForceDeleteConfirmDialog'
|
|
24
26
|
export { ToastProvider, useToast, showApiError, showApiSuccess } from './Toast'
|
|
@@ -38,3 +40,7 @@ export { DistributionBar, DistributionLegendChip } from './DistributionBar'
|
|
|
38
40
|
export type { DistributionSegment } from './DistributionBar'
|
|
39
41
|
export { SearchPillInput } from './SearchPillInput'
|
|
40
42
|
export type { SearchModifier, SearchPillInputProps } from './SearchPillInput'
|
|
43
|
+
export { CardSection, CardBody, TerminalBlock, renderProse, NEUTRAL_CHIP_CLASS, KIND_CHIP_CLASS } from './CardSection'
|
|
44
|
+
export type { CardSectionTone } from './CardSection'
|
|
45
|
+
export { MultiSelectPicker } from './MultiSelectPicker'
|
|
46
|
+
export type { MultiSelectPickerProps } from './MultiSelectPicker'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Shared severity color vocabulary. The Issue card (2-tier: critical/warning)
|
|
2
|
+
// and the Check card (4-tier: critical/high/medium/low) are different severity
|
|
3
|
+
// axes, but every tier resolves to one of four hues — so the color strings live
|
|
4
|
+
// here once and each card maps its own severity enum onto a tone. A palette
|
|
5
|
+
// change lands in one place instead of drifting between the two cards.
|
|
6
|
+
//
|
|
7
|
+
// Literal class strings (no template interpolation) so Tailwind's scanner sees
|
|
8
|
+
// every utility.
|
|
9
|
+
|
|
10
|
+
export type SeverityTone = 'red' | 'orange' | 'amber' | 'yellow' | 'slate'
|
|
11
|
+
|
|
12
|
+
// Text color — leading severity glyph + emphasis text. Yellow takes the darker
|
|
13
|
+
// -700 step in light mode: its low luminance is illegible at -600 on a light
|
|
14
|
+
// surface, where red/orange/amber still clear.
|
|
15
|
+
export const TONE_TEXT_CLASS: Record<SeverityTone, string> = {
|
|
16
|
+
red: 'text-red-600 dark:text-red-400',
|
|
17
|
+
orange: 'text-orange-600 dark:text-orange-400',
|
|
18
|
+
amber: 'text-amber-600 dark:text-amber-400',
|
|
19
|
+
yellow: 'text-yellow-700 dark:text-yellow-400',
|
|
20
|
+
slate: 'text-slate-500 dark:text-slate-400',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Solid fill — dots + the proportional distribution-bar segments.
|
|
24
|
+
export const TONE_FILL_CLASS: Record<SeverityTone, string> = {
|
|
25
|
+
red: 'bg-red-500',
|
|
26
|
+
orange: 'bg-orange-500',
|
|
27
|
+
amber: 'bg-amber-500',
|
|
28
|
+
yellow: 'bg-yellow-500',
|
|
29
|
+
slate: 'bg-slate-400',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Left accent rail on a collapsed queue row — the scan-down severity cue + a
|
|
33
|
+
// faint hover tint. The expanded row swaps this for the header band below.
|
|
34
|
+
export const TONE_RAIL_CLASS: Record<SeverityTone, string> = {
|
|
35
|
+
red: 'border-l-red-500 hover:bg-red-50/40 dark:hover:bg-red-950/20',
|
|
36
|
+
orange: 'border-l-orange-500 hover:bg-orange-50/40 dark:hover:bg-orange-950/20',
|
|
37
|
+
amber: 'border-l-amber-500 hover:bg-amber-50/30 dark:hover:bg-amber-950/15',
|
|
38
|
+
yellow: 'border-l-yellow-500 hover:bg-yellow-50/30 dark:hover:bg-yellow-950/15',
|
|
39
|
+
slate: 'border-l-slate-300 dark:border-l-slate-600 hover:bg-theme-hover/40',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Solid severity pill — the loud focus signal on an expanded card's header band
|
|
43
|
+
// (collapsed cards keep the soft BADGE pill so a long queue stays calm).
|
|
44
|
+
// The warm mid-tones (orange/amber/yellow) take dark text: white on them sits
|
|
45
|
+
// below WCAG for the 12px pill text.
|
|
46
|
+
export const TONE_SOLID_CLASS: Record<SeverityTone, string> = {
|
|
47
|
+
red: 'bg-red-600 text-white',
|
|
48
|
+
orange: 'bg-orange-500 text-orange-950',
|
|
49
|
+
amber: 'bg-amber-400 text-amber-950',
|
|
50
|
+
yellow: 'bg-yellow-400 text-yellow-950',
|
|
51
|
+
slate: 'bg-slate-500 text-white dark:bg-slate-600',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Severity-tinted header band, shown only when a card is expanded. The left
|
|
55
|
+
// rail keeps its color so the queue's scan-down severity rhythm doesn't break
|
|
56
|
+
// at the focused row; no hover variant, so it doesn't lighten on hover.
|
|
57
|
+
export const TONE_HEADER_BAND_CLASS: Record<SeverityTone, string> = {
|
|
58
|
+
red: 'border-l-red-500 bg-red-50 dark:bg-red-950/40',
|
|
59
|
+
orange: 'border-l-orange-500 bg-orange-50 dark:bg-orange-950/30',
|
|
60
|
+
amber: 'border-l-amber-500 bg-amber-50 dark:bg-amber-950/30',
|
|
61
|
+
yellow: 'border-l-yellow-500 bg-yellow-50 dark:bg-yellow-950/30',
|
|
62
|
+
slate: 'border-l-slate-300 dark:border-l-slate-600 bg-slate-50 dark:bg-slate-900/40',
|
|
63
|
+
}
|
|
@@ -32,6 +32,9 @@ interface ResourceDetailDrawerProps {
|
|
|
32
32
|
headerHeight?: number
|
|
33
33
|
/** Left offset to exclude (e.g. sidebar width) so expanded mode doesn't cover the sidebar (default: 0) */
|
|
34
34
|
leftOffset?: number
|
|
35
|
+
/** Right inset in px to leave clear (e.g. a docked side panel) so the drawer sits
|
|
36
|
+
* beside it instead of under it. Shared "right-side surface" contract (default: 0). */
|
|
37
|
+
rightInset?: number
|
|
35
38
|
/** Render the content inside the drawer */
|
|
36
39
|
children: (props: {
|
|
37
40
|
resource: SelectedResource
|
|
@@ -81,7 +84,7 @@ function usePrefersReducedMotion(): boolean {
|
|
|
81
84
|
return reduced
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
export function ResourceDetailDrawer({ resource, onClose, onNavigate, initialTab, isOpen = true, expanded, onCollapse, onExpand, canCollapseToDrawer = true, onNavigateToResource, headerHeight: headerHeightProp, leftOffset = 0, children }: ResourceDetailDrawerProps) {
|
|
87
|
+
export function ResourceDetailDrawer({ resource, onClose, onNavigate, initialTab, isOpen = true, expanded, onCollapse, onExpand, canCollapseToDrawer = true, onNavigateToResource, headerHeight: headerHeightProp, leftOffset = 0, rightInset = 0, children }: ResourceDetailDrawerProps) {
|
|
85
88
|
const [drawerWidth, setDrawerWidth] = useState(() => getDefaultWidth(resource.kind))
|
|
86
89
|
const [isResizing, setIsResizing] = useState(false)
|
|
87
90
|
const resizeStartX = useRef(0)
|
|
@@ -225,7 +228,7 @@ export function ResourceDetailDrawer({ resource, onClose, onNavigate, initialTab
|
|
|
225
228
|
// During the pre-mount frames (transitioning but not yet armed) hold the frame
|
|
226
229
|
// at its START width so the width transition fires only once the incoming
|
|
227
230
|
// layer has painted; otherwise it snaps to the target.
|
|
228
|
-
const fullW = `calc(100% - ${leftOffset}px)`
|
|
231
|
+
const fullW = `calc(100% - ${leftOffset}px - ${rightInset}px)`
|
|
229
232
|
const startWidth = settledExpanded.current ? fullW : drawerWidth
|
|
230
233
|
const targetWidth = expanded ? fullW : drawerWidth
|
|
231
234
|
const containerWidth = transitioning && !crossfadeArmed ? startWidth : targetWidth
|
|
@@ -274,6 +277,7 @@ export function ResourceDetailDrawer({ resource, onClose, onNavigate, initialTab
|
|
|
274
277
|
style={{
|
|
275
278
|
width: containerWidth,
|
|
276
279
|
top: headerHeight,
|
|
280
|
+
right: rightInset, // leave room for a docked side panel (AI), else flush right
|
|
277
281
|
height: `calc(100% - ${headerHeight}px - ${dockInset}px)`,
|
|
278
282
|
// Inline so the morph duration/easing is controlled precisely (and stays
|
|
279
283
|
// in lockstep with the crossfade + JS window). Width only animates when
|