@skyhook-io/k8s-ui 1.17.1 → 1.18.1
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/README.md +13 -0
- package/package.json +4 -4
- package/src/components/compare/CompareResourcePicker.tsx +18 -4
- package/src/components/gitops/GitOpsDetailLayout.test.tsx +64 -0
- package/src/components/gitops/GitOpsDetailLayout.tsx +22 -15
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +2 -2
- package/src/components/gitops/GitOpsTableView.coverage.test.tsx +27 -0
- package/src/components/gitops/GitOpsTableView.tsx +33 -4
- package/src/components/gitops/detail-helpers.test.ts +58 -13
- package/src/components/gitops/detail-helpers.ts +37 -6
- package/src/components/gitops/index.ts +1 -0
- package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +27 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +16 -10
- package/src/components/gitops/insights/insights-helpers.test.ts +18 -0
- package/src/components/gitops/insights/insights-helpers.ts +7 -0
- package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +73 -9
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +45 -28
- package/src/components/gitops/tree/tree-helpers.test.ts +6 -0
- package/src/components/gitops/tree/tree-helpers.ts +5 -1
- package/src/components/issues/IssuesView.tsx +18 -10
- package/src/components/issues/ResourceIssuesSection.tsx +5 -2
- package/src/components/issues/diagnostic.ts +2 -0
- package/src/components/issues/index.ts +1 -1
- package/src/components/issues/issues.test.ts +26 -0
- package/src/components/issues/severity.ts +9 -2
- package/src/components/issues/types.ts +32 -4
- package/src/components/logs/LogCore.theme.test.tsx +126 -1
- package/src/components/logs/LogCore.tsx +159 -40
- package/src/components/logs/LogsViewer.test.tsx +183 -60
- package/src/components/logs/LogsViewer.tsx +49 -12
- package/src/components/logs/StructuredLogLine.tsx +1 -1
- package/src/components/logs/WorkloadLogsViewer.tsx +7 -1
- package/src/components/logs/useLogBuffer.reading.test.tsx +149 -0
- package/src/components/logs/useLogBuffer.ts +48 -18
- package/src/components/logs/useLogSearch.ts +3 -1
- package/src/components/namespace-switcher/NamespacePicker.test.tsx +236 -2
- package/src/components/namespace-switcher/NamespacePicker.tsx +42 -8
- package/src/components/resources/ColumnPickerList.test.tsx +46 -0
- package/src/components/resources/ColumnPickerList.tsx +174 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +49 -0
- package/src/components/resources/ResourcesSidebar.tsx +36 -21
- package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -2
- package/src/components/resources/ResourcesView.selected-kind.test.ts +14 -0
- package/src/components/resources/ResourcesView.trivy-search.test.tsx +59 -0
- package/src/components/resources/ResourcesView.tsx +198 -93
- package/src/components/resources/column-order.test.ts +104 -0
- package/src/components/resources/column-order.ts +64 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.test.tsx +15 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +4 -1
- package/src/components/resources/renderers/CronJobRenderer.test.tsx +31 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +28 -20
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +22 -9
- package/src/components/resources/renderers/PodRenderer.sidecar-terminal.test.tsx +200 -0
- package/src/components/resources/renderers/PodRenderer.test.tsx +115 -0
- package/src/components/resources/renderers/PodRenderer.tsx +72 -20
- package/src/components/resources/resource-utils-trivy.test.ts +87 -0
- package/src/components/resources/resource-utils-trivy.ts +122 -0
- package/src/components/resources/resource-utils.ts +15 -2
- package/src/components/resources/summarize-scheduler-message.test.ts +1 -0
- package/src/components/shared/DetailShell.tsx +9 -7
- package/src/components/shared/DrainPlanDialog.test.tsx +7 -0
- package/src/components/shared/DrainPlanDialog.tsx +6 -2
- package/src/components/shared/ManagedByChip.test.tsx +22 -0
- package/src/components/shared/ManagedByChip.tsx +24 -4
- package/src/components/shared/ResourceActionsBar.identity.test.tsx +64 -0
- package/src/components/shared/ResourceActionsBar.tsx +12 -6
- package/src/components/shared/ResourceRendererDispatch.test.tsx +49 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +48 -31
- package/src/components/timeline/DiffViewer.test.tsx +69 -0
- package/src/components/timeline/DiffViewer.tsx +113 -4
- package/src/components/timeline/TimelineList.tsx +5 -1
- package/src/components/timeline/shared.test.tsx +48 -0
- package/src/components/timeline/shared.tsx +14 -19
- package/src/components/ui/FetchResult.test.tsx +19 -2
- package/src/components/ui/FetchResult.tsx +13 -1
- package/src/components/ui/MultiSelectPicker.test.tsx +20 -0
- package/src/components/ui/MultiSelectPicker.tsx +20 -2
- package/src/components/ui/PageHeader.tsx +6 -4
- package/src/components/ui/Toast.tsx +48 -30
- package/src/components/ui/drawer-components.test.tsx +253 -0
- package/src/components/ui/drawer-components.tsx +315 -11
- package/src/components/ui/drawer-older-replicasets.test.tsx +45 -0
- package/src/components/ui/index.ts +1 -1
- package/src/components/workload/WorkloadView.relationships.test.tsx +33 -0
- package/src/components/workload/WorkloadView.timelineFocus.test.tsx +96 -0
- package/src/components/workload/WorkloadView.tsx +68 -14
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useMediaQuery.ts +21 -0
- package/src/hooks/usePhoneLayout.ts +12 -0
- package/src/theme/tailwind-theme.css +5 -0
- package/src/types/core.ts +91 -3
- package/src/types/gitops-flux-inventory.test.ts +24 -0
- package/src/types/gitops-insights.ts +1 -0
- package/src/types/gitops-tree.ts +2 -0
- package/src/types/gitops.ts +5 -5
- package/src/types/index.ts +3 -0
- package/src/types/read-coverage.ts +65 -0
- package/src/types/resource-coverage.ts +40 -0
- package/src/utils/format.test.ts +7 -0
- package/src/utils/format.ts +6 -1
- package/src/utils/gitops-owner.test.ts +42 -1
- package/src/utils/gitops-owner.ts +22 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/read-actions.test.ts +22 -0
- package/src/utils/read-actions.ts +56 -0
- package/src/utils/resource-coverage.test.ts +121 -0
- package/src/utils/resource-coverage.ts +117 -0
|
@@ -43,7 +43,7 @@ import type { NavigateToResource } from '../../utils/navigation'
|
|
|
43
43
|
import { refToSelectedResource, pluralToKind, knownKindForPluralWithGroup, kindToPlural, kindToPluralWithGroup, apiVersionToGroup } from '../../utils/navigation'
|
|
44
44
|
import { neighborhoodFor, seedNodeIds, topologyNodeResourceKind } from '../../utils/topology-neighborhood'
|
|
45
45
|
import { TopologyGraph } from '../topology/TopologyGraph'
|
|
46
|
-
import { gitOpsOwnerFromRelationships, type GitOpsOwnerRef } from '../../utils/gitops-owner'
|
|
46
|
+
import { gitOpsOwnerFromRelationships, managerListing, type GitOpsOwnerRef } from '../../utils/gitops-owner'
|
|
47
47
|
import { gitOpsRouteForResource } from '../../utils/gitops-route'
|
|
48
48
|
import { buildResourceHierarchy, getAllEventsFromHierarchy, isProblematicEvent, type ResourceLane } from '../../utils/resource-hierarchy'
|
|
49
49
|
import { TimelineSwimlanes, type TimeWindow } from '../timeline/TimelineSwimlanes'
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
import { ResourceRendererDispatch, getResourceStatus, diagnoseHealthHint, type DiagnoseHealthHint, type RendererOverrides } from '../shared/ResourceRendererDispatch'
|
|
58
58
|
import type { ScalerDiagnosis } from '../resources/renderers/WorkloadRenderer'
|
|
59
59
|
import { DetailShell, type DetailShellTab } from '../shared/DetailShell'
|
|
60
|
-
import { HelmManagedByChip, ManagedByChip, type HelmOwnerRef } from '../shared/ManagedByChip'
|
|
60
|
+
import { gitOpsOwnerKindLabel, HelmManagedByChip, ManagedByChip, type HelmOwnerRef } from '../shared/ManagedByChip'
|
|
61
61
|
import { AlertBanner, getKindColorOutline, displayKindName, OperationalIssuesShownContext, ResourceRefBadge } from '../ui/drawer-components'
|
|
62
62
|
import { Badge, type BadgeSeverity } from '../ui/Badge'
|
|
63
63
|
import { Tooltip } from '../ui/Tooltip'
|
|
@@ -216,6 +216,8 @@ interface WorkloadViewProps {
|
|
|
216
216
|
/** Timeline events the history lanes are built from: this workload's own
|
|
217
217
|
* history, or events for its namespace from a host that has no scoped query. */
|
|
218
218
|
allEvents?: TimelineEvent[]
|
|
219
|
+
/** Reveals one event, loading older pages if needed. Use a new object for each navigation request. */
|
|
220
|
+
timelineFocusEvent?: { id: string }
|
|
219
221
|
/** Older history exists beyond allEvents. */
|
|
220
222
|
historyTruncated?: boolean
|
|
221
223
|
/** Loads the next older page of history; omit when the host can't page. */
|
|
@@ -353,6 +355,8 @@ interface WorkloadViewProps {
|
|
|
353
355
|
namespace: string
|
|
354
356
|
name: string
|
|
355
357
|
resource: any
|
|
358
|
+
/** Place resource notices within the replacement layout's content area. */
|
|
359
|
+
leadContent?: ReactNode
|
|
356
360
|
}) => ReactNode
|
|
357
361
|
/** Render the metrics tab content */
|
|
358
362
|
renderMetricsTab?: (props: { kind: string; namespace: string; name: string }) => ReactNode
|
|
@@ -398,6 +402,8 @@ interface WorkloadViewProps {
|
|
|
398
402
|
* live issues for this resource). Avoids showing the same failure twice.
|
|
399
403
|
* Also gates the `renderOverviewLead` wrapper (see above). */
|
|
400
404
|
hasOperationalIssues?: boolean
|
|
405
|
+
/** Also show the lead for neutral context without suppressing renderer problems. */
|
|
406
|
+
hasOverviewLead?: boolean
|
|
401
407
|
/** True while the live-issues fetch is still pending. Renderers suppress their
|
|
402
408
|
* banners during this window too — a banner whose failure the pipeline covers
|
|
403
409
|
* would otherwise flash on first paint before the issues arrive and hide it. */
|
|
@@ -465,6 +471,7 @@ export function WorkloadView({
|
|
|
465
471
|
refetch: refetchProp,
|
|
466
472
|
// Timeline
|
|
467
473
|
allEvents,
|
|
474
|
+
timelineFocusEvent,
|
|
468
475
|
historyTruncated = false,
|
|
469
476
|
onLoadOlderHistory,
|
|
470
477
|
loadingOlderHistory = false,
|
|
@@ -518,6 +525,7 @@ export function WorkloadView({
|
|
|
518
525
|
renderOverviewIntro,
|
|
519
526
|
renderOverviewLead,
|
|
520
527
|
hasOperationalIssues,
|
|
528
|
+
hasOverviewLead,
|
|
521
529
|
operationalIssuesPending,
|
|
522
530
|
// Actions bar
|
|
523
531
|
actionsBarProps,
|
|
@@ -718,6 +726,18 @@ export function WorkloadView({
|
|
|
718
726
|
const metadata = useMemo(() => extractMetadata(kind, resource), [kind, resource])
|
|
719
727
|
const relationshipGitOpsOwner = useMemo(() => gitOpsOwnerFromRelationships(relationships), [relationships])
|
|
720
728
|
const gitopsOwner = resolvedGitOpsOwner ?? relationshipGitOpsOwner
|
|
729
|
+
const listing = useMemo(
|
|
730
|
+
() => managerListing(gitopsOwner, relationships?.managers, gitOpsOwnerVerified, !relationships?.ownership?.managers),
|
|
731
|
+
[gitopsOwner, relationships?.managers, relationships?.ownership?.managers, gitOpsOwnerVerified],
|
|
732
|
+
)
|
|
733
|
+
// When the chip's manager is the only one whose status lists the resource,
|
|
734
|
+
// the chip says it all: Related Resources would repeat it.
|
|
735
|
+
const rendererRelationships = useMemo(
|
|
736
|
+
() => (relationships && listing?.state === 'confirmed' ? { ...relationships, managers: undefined } : relationships),
|
|
737
|
+
[relationships, listing],
|
|
738
|
+
)
|
|
739
|
+
// When they disagree, Related Resources says which manager the label names.
|
|
740
|
+
const trackedBy = listing?.state === 'conflict' && gitopsOwner ? `${gitOpsOwnerKindLabel(gitopsOwner)} ${gitopsOwner.namespace ? `${gitopsOwner.namespace}/` : ''}${gitopsOwner.name}` : undefined
|
|
721
741
|
// When the resource itself is a portal GitOps CR (Application, Kustomization,
|
|
722
742
|
// HelmRelease, etc.), surface a link to its dedicated GitOps detail page —
|
|
723
743
|
// the drawer's renderer is thorough but the tab has the tree + insights +
|
|
@@ -930,7 +950,12 @@ export function WorkloadView({
|
|
|
930
950
|
useEffect(() => {
|
|
931
951
|
if (shouldCommitFallback && activeTab !== 'overview') onTabChange?.('overview', { replace: true })
|
|
932
952
|
}, [activeTab, onTabChange, shouldCommitFallback])
|
|
933
|
-
const expandedOverview = expanded ? renderExpandedOverview?.({
|
|
953
|
+
const expandedOverview = expanded ? renderExpandedOverview?.({
|
|
954
|
+
kind, apiKind, namespace, name, resource,
|
|
955
|
+
leadContent: (hasOverviewLead ?? hasOperationalIssues) && renderOverviewLeadForState
|
|
956
|
+
? renderOverviewLeadForState({ kind, namespace, name })
|
|
957
|
+
: undefined,
|
|
958
|
+
}) : null
|
|
934
959
|
const overviewIntro = renderOverviewIntro?.({ kind, namespace, name })
|
|
935
960
|
|
|
936
961
|
// ── Collapsed (drawer) mode ──────────────────────────────────────────────
|
|
@@ -1012,7 +1037,7 @@ export function WorkloadView({
|
|
|
1012
1037
|
<p className="text-sm text-theme-text-tertiary">{namespace}</p>
|
|
1013
1038
|
{(gitopsOwner || helmOwner || (gitOpsResourcePath && onNavigateGitOpsPath) || onOpenLiveTraffic) && (
|
|
1014
1039
|
<div className="mt-1 flex flex-wrap items-center gap-1.5">
|
|
1015
|
-
{gitopsOwner && <ManagedByChip owner={gitopsOwner} status={gitOpsOwnerStatus} verified={gitOpsOwnerVerified} pending={gitOpsOwnerPending} source={gitOpsOwnerSource} onOpen={onOpenGitOpsResource} />}
|
|
1040
|
+
{gitopsOwner && <ManagedByChip owner={gitopsOwner} status={gitOpsOwnerStatus} verified={gitOpsOwnerVerified} pending={gitOpsOwnerPending} source={gitOpsOwnerSource} listing={listing} onOpen={onOpenGitOpsResource} />}
|
|
1016
1041
|
{helmOwner && <HelmManagedByChip owner={helmOwner} source={helmOwnerSource} onOpen={onOpenHelmRelease} />}
|
|
1017
1042
|
{gitOpsResourcePath && onNavigateGitOpsPath && (
|
|
1018
1043
|
<OpenInGitOpsChip onClick={() => onNavigateGitOpsPath(gitOpsResourcePath)} />
|
|
@@ -1090,15 +1115,17 @@ export function WorkloadView({
|
|
|
1090
1115
|
drawerSummary
|
|
1091
1116
|
) : (
|
|
1092
1117
|
<>
|
|
1093
|
-
{renderOverviewLeadForState && hasOperationalIssues && (
|
|
1118
|
+
{renderOverviewLeadForState && (hasOverviewLead ?? hasOperationalIssues) && (
|
|
1094
1119
|
<div className="px-4 pt-4">
|
|
1095
1120
|
{renderOverviewLeadForState({ kind, namespace, name })}
|
|
1096
1121
|
</div>
|
|
1097
1122
|
)}
|
|
1123
|
+
{overviewIntro && <div className="px-4 pt-4">{overviewIntro}</div>}
|
|
1098
1124
|
<ResourceRendererDispatch
|
|
1099
1125
|
resource={selectedResource}
|
|
1100
1126
|
data={resource}
|
|
1101
|
-
relationships={
|
|
1127
|
+
relationships={rendererRelationships}
|
|
1128
|
+
trackedBy={trackedBy}
|
|
1102
1129
|
certificateInfo={certificateInfo}
|
|
1103
1130
|
hpaDiagnosis={hpaDiagnosis}
|
|
1104
1131
|
scalerDiagnostics={scalerDiagnostics}
|
|
@@ -1196,7 +1223,7 @@ export function WorkloadView({
|
|
|
1196
1223
|
</Tooltip>
|
|
1197
1224
|
),
|
|
1198
1225
|
gitopsOwner && (
|
|
1199
|
-
<ManagedByChip key="gitops" owner={gitopsOwner} status={gitOpsOwnerStatus} verified={gitOpsOwnerVerified} pending={gitOpsOwnerPending} source={gitOpsOwnerSource} onOpen={onOpenGitOpsResource} variant="block" />
|
|
1226
|
+
<ManagedByChip key="gitops" owner={gitopsOwner} status={gitOpsOwnerStatus} verified={gitOpsOwnerVerified} pending={gitOpsOwnerPending} source={gitOpsOwnerSource} listing={listing} onOpen={onOpenGitOpsResource} variant="block" />
|
|
1200
1227
|
),
|
|
1201
1228
|
helmOwner && (
|
|
1202
1229
|
<HelmManagedByChip key="helm" owner={helmOwner} source={helmOwnerSource} onOpen={onOpenHelmRelease} variant="block" />
|
|
@@ -1303,11 +1330,6 @@ export function WorkloadView({
|
|
|
1303
1330
|
<div className="h-full min-h-0 overflow-y-auto">{expandedSummary}</div>
|
|
1304
1331
|
) : effectiveTab === 'overview' && expandedOverview ? (
|
|
1305
1332
|
<div className="h-full min-h-0">
|
|
1306
|
-
{hasOperationalIssues && renderOverviewLeadForState && (
|
|
1307
|
-
<div className="px-4 pt-4">
|
|
1308
|
-
{renderOverviewLeadForState({ kind, namespace, name })}
|
|
1309
|
-
</div>
|
|
1310
|
-
)}
|
|
1311
1333
|
{expandedOverview}
|
|
1312
1334
|
</div>
|
|
1313
1335
|
) : effectiveTab === 'spec' && specTab?.render && !resourceGone ? (
|
|
@@ -1316,7 +1338,8 @@ export function WorkloadView({
|
|
|
1316
1338
|
<InfoTab
|
|
1317
1339
|
resource={resource}
|
|
1318
1340
|
selectedResource={selectedResource}
|
|
1319
|
-
relationships={
|
|
1341
|
+
relationships={rendererRelationships}
|
|
1342
|
+
trackedBy={trackedBy}
|
|
1320
1343
|
certificateInfo={certificateInfo}
|
|
1321
1344
|
hpaDiagnosis={hpaDiagnosis}
|
|
1322
1345
|
scalerDiagnostics={scalerDiagnostics}
|
|
@@ -1350,7 +1373,7 @@ export function WorkloadView({
|
|
|
1350
1373
|
// A host's spec lead replaces the operational issues there: its Overview already leads with them.
|
|
1351
1374
|
effectiveTab === 'spec' && specTab?.lead
|
|
1352
1375
|
? specTab.lead
|
|
1353
|
-
: hasOperationalIssues && renderOverviewLeadForState
|
|
1376
|
+
: (hasOverviewLead ?? hasOperationalIssues) && renderOverviewLeadForState
|
|
1354
1377
|
? renderOverviewLeadForState({ kind, namespace, name })
|
|
1355
1378
|
: undefined
|
|
1356
1379
|
}
|
|
@@ -1377,6 +1400,7 @@ export function WorkloadView({
|
|
|
1377
1400
|
)}
|
|
1378
1401
|
{effectiveTab === 'timeline' && (
|
|
1379
1402
|
<EventsTab
|
|
1403
|
+
focusEvent={timelineFocusEvent}
|
|
1380
1404
|
events={resourceEvents}
|
|
1381
1405
|
isLoading={eventsLoading}
|
|
1382
1406
|
truncated={historyTruncated}
|
|
@@ -1682,6 +1706,7 @@ function OwnershipHeading({
|
|
|
1682
1706
|
// ============================================================================
|
|
1683
1707
|
|
|
1684
1708
|
function EventsTab({
|
|
1709
|
+
focusEvent,
|
|
1685
1710
|
events,
|
|
1686
1711
|
isLoading,
|
|
1687
1712
|
truncated,
|
|
@@ -1697,6 +1722,7 @@ function EventsTab({
|
|
|
1697
1722
|
topology,
|
|
1698
1723
|
onResourceClick,
|
|
1699
1724
|
}: {
|
|
1725
|
+
focusEvent?: { id: string }
|
|
1700
1726
|
events: TimelineEvent[]
|
|
1701
1727
|
isLoading: boolean
|
|
1702
1728
|
truncated?: boolean
|
|
@@ -1792,6 +1818,22 @@ function EventsTab({
|
|
|
1792
1818
|
onSelectEvent(id)
|
|
1793
1819
|
}, [onSelectEvent])
|
|
1794
1820
|
|
|
1821
|
+
const focusedRequest = useRef<typeof focusEvent>(undefined)
|
|
1822
|
+
const requestedPage = useRef<{ focus: typeof focusEvent; events: TimelineEvent[] } | undefined>(undefined)
|
|
1823
|
+
const focusFound = Boolean(focusEvent && cleanEvents.some((event) => event.id === focusEvent.id))
|
|
1824
|
+
useEffect(() => {
|
|
1825
|
+
if (!focusEvent || focusedRequest.current === focusEvent || isLoading) return
|
|
1826
|
+
if (focusFound) {
|
|
1827
|
+
handleSelect(focusEvent.id)
|
|
1828
|
+
focusedRequest.current = focusEvent
|
|
1829
|
+
} else if (truncated && onLoadOlder && !loadingOlder && !olderError && !error) {
|
|
1830
|
+
// Strict Mode can replay this effect before the loading prop updates.
|
|
1831
|
+
if (requestedPage.current?.focus === focusEvent && requestedPage.current.events === events) return
|
|
1832
|
+
requestedPage.current = { focus: focusEvent, events }
|
|
1833
|
+
onLoadOlder()
|
|
1834
|
+
}
|
|
1835
|
+
}, [focusEvent, focusFound, events, isLoading, truncated, onLoadOlder, loadingOlder, olderError, error, handleSelect])
|
|
1836
|
+
|
|
1795
1837
|
if (isLoading) {
|
|
1796
1838
|
return (
|
|
1797
1839
|
<div className="flex items-center justify-center h-full text-theme-text-tertiary">
|
|
@@ -1821,6 +1863,15 @@ function EventsTab({
|
|
|
1821
1863
|
|
|
1822
1864
|
return (
|
|
1823
1865
|
<div className="h-full flex flex-col overflow-hidden">
|
|
1866
|
+
{focusEvent && !focusFound && (
|
|
1867
|
+
<div className="shrink-0 border-b border-theme-border px-4 py-2 text-xs text-theme-text-secondary" role="status">
|
|
1868
|
+
{olderError || error
|
|
1869
|
+
? "Couldn't find the reported error because some history couldn't be loaded."
|
|
1870
|
+
: truncated && onLoadOlder
|
|
1871
|
+
? 'Finding the reported error in older history…'
|
|
1872
|
+
: 'The reported error is not available in the retained timeline history.'}
|
|
1873
|
+
</div>
|
|
1874
|
+
)}
|
|
1824
1875
|
{error && (
|
|
1825
1876
|
<div className="shrink-0 flex flex-wrap items-center gap-x-2 border-b border-theme-border bg-theme-base px-4 py-1.5 text-xs" role="alert">
|
|
1826
1877
|
<span className="text-[var(--color-error)]" title={error.message}>
|
|
@@ -1926,6 +1977,7 @@ function InfoTab({
|
|
|
1926
1977
|
resource,
|
|
1927
1978
|
selectedResource,
|
|
1928
1979
|
relationships,
|
|
1980
|
+
trackedBy,
|
|
1929
1981
|
certificateInfo,
|
|
1930
1982
|
hpaDiagnosis,
|
|
1931
1983
|
scalerDiagnostics,
|
|
@@ -1962,6 +2014,7 @@ function InfoTab({
|
|
|
1962
2014
|
resource: any
|
|
1963
2015
|
selectedResource: SelectedResource
|
|
1964
2016
|
relationships?: Relationships
|
|
2017
|
+
trackedBy?: string
|
|
1965
2018
|
certificateInfo?: any
|
|
1966
2019
|
hpaDiagnosis?: HPADiagnosis
|
|
1967
2020
|
scalerDiagnostics?: ScalerDiagnosis[]
|
|
@@ -2016,6 +2069,7 @@ function InfoTab({
|
|
|
2016
2069
|
resource={selectedResource}
|
|
2017
2070
|
data={resource}
|
|
2018
2071
|
relationships={relationships}
|
|
2072
|
+
trackedBy={trackedBy}
|
|
2019
2073
|
certificateInfo={certificateInfo}
|
|
2020
2074
|
hpaDiagnosis={hpaDiagnosis}
|
|
2021
2075
|
scalerDiagnostics={scalerDiagnostics}
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { useAnimatedUnmount } from './useAnimatedUnmount'
|
|
2
2
|
export { useDebouncedValue } from './useDebouncedValue'
|
|
3
3
|
export { useRefreshAnimation } from './useRefreshAnimation'
|
|
4
|
+
export { useMediaQuery } from './useMediaQuery'
|
|
5
|
+
export { usePhoneLayout, PHONE_MEDIA_QUERY } from './usePhoneLayout'
|
|
4
6
|
export {
|
|
5
7
|
KeyboardShortcutProvider,
|
|
6
8
|
useRegisterShortcut,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
// Subscribe to a CSS media query. SSR-safe (returns false before mount) and
|
|
4
|
+
// updates on viewport changes.
|
|
5
|
+
export function useMediaQuery(query: string): boolean {
|
|
6
|
+
const [matches, setMatches] = useState(() => {
|
|
7
|
+
if (typeof window === 'undefined' || !window.matchMedia) return false
|
|
8
|
+
return window.matchMedia(query).matches
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (typeof window === 'undefined' || !window.matchMedia) return
|
|
13
|
+
const mql = window.matchMedia(query)
|
|
14
|
+
const onChange = () => setMatches(mql.matches)
|
|
15
|
+
onChange()
|
|
16
|
+
mql.addEventListener('change', onChange)
|
|
17
|
+
return () => mql.removeEventListener('change', onChange)
|
|
18
|
+
}, [query])
|
|
19
|
+
|
|
20
|
+
return matches
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useMediaQuery } from './useMediaQuery'
|
|
2
|
+
|
|
3
|
+
// A phone: a narrow screen whose primary pointer is touch. A desktop browser
|
|
4
|
+
// narrowed below this width keeps the desktop layout (and its content floor),
|
|
5
|
+
// since a wider window can show the full UI; browser dev-tools device mode
|
|
6
|
+
// emulates touch and gets the phone layout. Keep in sync with the `phone:`
|
|
7
|
+
// variant in theme/tailwind-theme.css.
|
|
8
|
+
export const PHONE_MEDIA_QUERY = '(max-width: 639px) and (pointer: coarse)'
|
|
9
|
+
|
|
10
|
+
export function usePhoneLayout(): boolean {
|
|
11
|
+
return useMediaQuery(PHONE_MEDIA_QUERY)
|
|
12
|
+
}
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* Import after variables.css.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
/* `phone:` styles apply on a narrow touch-first screen only (see
|
|
10
|
+
* PHONE_MEDIA_QUERY in hooks/usePhoneLayout.ts). A narrowed desktop window
|
|
11
|
+
* keeps the desktop layout. */
|
|
12
|
+
@custom-variant phone (@media (max-width: 639px) and (pointer: coarse));
|
|
13
|
+
|
|
9
14
|
@theme inline {
|
|
10
15
|
/* Backgrounds */
|
|
11
16
|
--color-theme-sidebar: var(--bg-sidebar);
|
package/src/types/core.ts
CHANGED
|
@@ -126,6 +126,7 @@ export interface Capabilities {
|
|
|
126
126
|
helmWrite: boolean // Helm write operations (install, upgrade, rollback, uninstall, apply values)
|
|
127
127
|
nodeWrite: boolean // Node write operations (cordon, uncordon, drain)
|
|
128
128
|
workloadWrites?: WorkloadWritePermissions // Workload patch permissions (restart/scale controls)
|
|
129
|
+
mcpReadOnly?: boolean // Full MCP endpoint is disabled; read-only endpoints remain available
|
|
129
130
|
mcpEnabled: boolean // MCP server is running
|
|
130
131
|
// Karpenter discovery and NodePool read state. Optional on the wire for the
|
|
131
132
|
// same newer-frontend/older-backend reason as `deployment` below — consumers
|
|
@@ -160,6 +161,9 @@ export interface CloudConnectCapability {
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
export interface FeatureCapabilities {
|
|
164
|
+
helmSourceIdentity?: boolean
|
|
165
|
+
resourceReadCoverage?: boolean
|
|
166
|
+
searchCoverage?: boolean
|
|
163
167
|
yamlReview?: boolean
|
|
164
168
|
yamlSchemas?: boolean
|
|
165
169
|
workloadImages?: boolean
|
|
@@ -650,10 +654,23 @@ export interface Relationships {
|
|
|
650
654
|
sourceResourceVersion?: string
|
|
651
655
|
mirrors?: ResourceRef[]
|
|
652
656
|
}
|
|
653
|
-
owner?: ResourceRef
|
|
657
|
+
owner?: ResourceRef // The owner Kubernetes acts through: the controller reference, else the only reference
|
|
658
|
+
owners?: ResourceRef[] // Every owner reference that resolves to an existing owner
|
|
654
659
|
deployment?: ResourceRef // Grandparent Deployment (for Pods owned by ReplicaSets)
|
|
655
660
|
managedBy?: ResourceRef[] // Topmost meaningful manager(s): GitOps controller (ArgoCD Application / Flux Kustomization / Flux HelmRelease), Helm release, or the topmost K8s owner. Synthesized server-side; replaces client-side detectGitOpsOwner.
|
|
656
|
-
children?: ResourceRef[]
|
|
661
|
+
children?: ResourceRef[] // Objects whose owner references name this one, less olderRevisions
|
|
662
|
+
// Children kept apart because they only keep history: ReplicaSets that run
|
|
663
|
+
// no Pods and aren't their owner's current revision. ownership.children
|
|
664
|
+
// covers both lists together.
|
|
665
|
+
olderRevisions?: ResourceRef[]
|
|
666
|
+
// Management without an owner reference: GitOps member lists, class
|
|
667
|
+
// bindings, spec and status references. Never ownership or garbage collection.
|
|
668
|
+
managers?: ResourceRef[]
|
|
669
|
+
manages?: ResourceRef[]
|
|
670
|
+
/** How complete the owner-reference and recorded-membership answers are.
|
|
671
|
+
* Absent: no completeness is claimed, whether because the server answers
|
|
672
|
+
* Owner and Children from graph edges or because the resource is an Event. */
|
|
673
|
+
ownership?: OwnershipCoverage
|
|
657
674
|
services?: ResourceRef[]
|
|
658
675
|
ingresses?: ResourceRef[]
|
|
659
676
|
gateways?: ResourceRef[]
|
|
@@ -680,6 +697,51 @@ export interface Relationships {
|
|
|
680
697
|
resourceClaims?: ResourceRef[] // For Pods: DRA ResourceClaims (direct + template-generated)
|
|
681
698
|
}
|
|
682
699
|
|
|
700
|
+
/** A kind Radar couldn't read, in namespace or (without one) in several or all of them, or the cluster for a cluster-scoped kind; reason is a readreason code. No kind stands for kinds discovery didn't list; no group is the core group. */
|
|
701
|
+
export interface OwnershipGap {
|
|
702
|
+
group?: string
|
|
703
|
+
kind?: string
|
|
704
|
+
namespace?: string
|
|
705
|
+
reason: string
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export interface OwnershipCoverage {
|
|
709
|
+
/** The owner reference's state when no owner is named: gone, replaced, withheld, unknown, unresolvable, excluded (it names an Event) or several. */
|
|
710
|
+
primaryOwnerState?: string
|
|
711
|
+
/** The owner the reference names when it is gone, replaced or unknown. */
|
|
712
|
+
ownerRef?: ResourceRef
|
|
713
|
+
/** A readreason code saying why an unknown owner couldn't be checked. */
|
|
714
|
+
ownerReason?: string
|
|
715
|
+
children: {
|
|
716
|
+
/** Children are every child the viewer may read. */
|
|
717
|
+
complete: boolean
|
|
718
|
+
notChecked?: OwnershipGap[]
|
|
719
|
+
truncated?: boolean
|
|
720
|
+
/** The viewer may read only some of the places children could be. */
|
|
721
|
+
narrowed?: boolean
|
|
722
|
+
}
|
|
723
|
+
/** For a GitOps manager whose status lists its members. */
|
|
724
|
+
manages?: {
|
|
725
|
+
membership: 'recorded' | 'remote' | 'unreadable'
|
|
726
|
+
/** A readreason code when the member list couldn't be read, as opposed to the status recording none. */
|
|
727
|
+
unavailableReason?: string
|
|
728
|
+
missing?: number
|
|
729
|
+
withheld?: number
|
|
730
|
+
unknown?: number
|
|
731
|
+
truncated?: boolean
|
|
732
|
+
}
|
|
733
|
+
/** Set when the search for the GitOps managers whose status lists this resource, for `managers`, couldn't settle every one the viewer may read. */
|
|
734
|
+
managers?: {
|
|
735
|
+
/** Managers with a member list Radar can't read, any of which may list this resource. */
|
|
736
|
+
unreadable?: number
|
|
737
|
+
/** Managers listing this resource's coordinates for a cluster Radar can't confirm is this one. */
|
|
738
|
+
remote?: number
|
|
739
|
+
notChecked?: OwnershipGap[]
|
|
740
|
+
}
|
|
741
|
+
/** A readreason code when Radar couldn't answer at all. */
|
|
742
|
+
unavailableReason?: string
|
|
743
|
+
}
|
|
744
|
+
|
|
683
745
|
// Parsed X.509 certificate metadata (from backend cert parsing)
|
|
684
746
|
export interface CertificateInfo {
|
|
685
747
|
subject: string
|
|
@@ -1089,7 +1151,7 @@ export interface UpgradeInfo {
|
|
|
1089
1151
|
error?: string
|
|
1090
1152
|
// Machine-readable source-resolution failure, used to make the Helm drawer
|
|
1091
1153
|
// explain whether tracking an OCI source can help.
|
|
1092
|
-
sourceIssue?: 'untracked' | 'repo_index_error' | 'ambiguous_repository' | 'ambiguous_source' | 'source_unavailable'
|
|
1154
|
+
sourceIssue?: 'source_confirmation_required' | 'source_change_required' | 'release_changing' | 'untracked' | 'repo_index_error' | 'ambiguous_repository' | 'ambiguous_source' | 'source_unavailable'
|
|
1093
1155
|
// True only when the error is a genuinely untracked source (registering a
|
|
1094
1156
|
// chart source could fix it). Kept for compatibility; prefer sourceIssue.
|
|
1095
1157
|
untracked?: boolean
|
|
@@ -1099,7 +1161,26 @@ export type ChartSourceCandidate =
|
|
|
1099
1161
|
| { type: 'repository'; reference: string; url: string }
|
|
1100
1162
|
| { type: 'oci'; reference: string; url?: never }
|
|
1101
1163
|
|
|
1164
|
+
export interface SourceUpgradeSelection {
|
|
1165
|
+
source: ChartSourceCandidate
|
|
1166
|
+
expectedRevision: number
|
|
1167
|
+
expectedSource: ChartSourceCandidate | null
|
|
1168
|
+
previewDigest?: string
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
export interface ChartSourceSuggestion {
|
|
1172
|
+
source: ChartSourceCandidate
|
|
1173
|
+
current: boolean
|
|
1174
|
+
hasInstalledVersion: boolean
|
|
1175
|
+
state: 'available' | 'index_unavailable' | 'unchecked'
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1102
1178
|
export interface ChartSourceStatus {
|
|
1179
|
+
changing?: boolean
|
|
1180
|
+
revision?: number
|
|
1181
|
+
confirmed?: boolean
|
|
1182
|
+
sourceChangeRequired?: boolean
|
|
1183
|
+
suggestions?: ChartSourceSuggestion[]
|
|
1103
1184
|
recorded?: ChartSourceCandidate
|
|
1104
1185
|
configured: boolean
|
|
1105
1186
|
available: boolean
|
|
@@ -1120,6 +1201,9 @@ export interface ApplyValuesRequest {
|
|
|
1120
1201
|
|
|
1121
1202
|
// Response for previewing values changes
|
|
1122
1203
|
export interface ValuesPreviewResponse {
|
|
1204
|
+
baselineSource?: ChartSourceCandidate
|
|
1205
|
+
previewDigest?: string
|
|
1206
|
+
baselineRevision?: number
|
|
1123
1207
|
currentValues: Record<string, unknown>
|
|
1124
1208
|
newValues: Record<string, unknown>
|
|
1125
1209
|
manifestDiff: string
|
|
@@ -1188,6 +1272,7 @@ export interface InstallChartRequest {
|
|
|
1188
1272
|
|
|
1189
1273
|
// ArtifactHub chart with rich metadata
|
|
1190
1274
|
export interface ArtifactHubChart {
|
|
1275
|
+
official?: boolean
|
|
1191
1276
|
packageId: string
|
|
1192
1277
|
name: string
|
|
1193
1278
|
version: string
|
|
@@ -1210,6 +1295,9 @@ export interface ArtifactHubChart {
|
|
|
1210
1295
|
|
|
1211
1296
|
// ArtifactHub repository info
|
|
1212
1297
|
export interface ArtifactHubRepository {
|
|
1298
|
+
organizationDisplayName?: string
|
|
1299
|
+
userAlias?: string
|
|
1300
|
+
displayName?: string
|
|
1213
1301
|
name: string
|
|
1214
1302
|
url: string
|
|
1215
1303
|
official?: boolean
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { parseFluxInventory } from './gitops'
|
|
3
|
+
|
|
4
|
+
// Mirrors TestParseFluxInventoryID in pkg/gitops/helpers_test.go: the
|
|
5
|
+
// Kustomization renderer and the Go tree read the same inventory IDs.
|
|
6
|
+
describe('parseFluxInventory', () => {
|
|
7
|
+
it('reads a colon in an RBAC name, written as a double underscore', () => {
|
|
8
|
+
expect(
|
|
9
|
+
parseFluxInventory([
|
|
10
|
+
{ id: '_system__metrics-server_rbac.authorization.k8s.io_ClusterRole', v: 'v1' },
|
|
11
|
+
{ id: 'kube-system_cert-manager__leaderelection_rbac.authorization.k8s.io_Role', v: 'v1' },
|
|
12
|
+
]),
|
|
13
|
+
).toEqual([
|
|
14
|
+
{ group: 'rbac.authorization.k8s.io', kind: 'ClusterRole', namespace: '', name: 'system:metrics-server' },
|
|
15
|
+
{ group: 'rbac.authorization.k8s.io', kind: 'Role', namespace: 'kube-system', name: 'cert-manager:leaderelection' },
|
|
16
|
+
])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('keeps single underscores in a name and the empty core group', () => {
|
|
20
|
+
expect(parseFluxInventory([{ id: 'ns_my_weird_name__ConfigMap' }])).toEqual([
|
|
21
|
+
{ group: '', kind: 'ConfigMap', namespace: 'ns', name: 'my_weird_name' },
|
|
22
|
+
])
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -170,6 +170,7 @@ export interface GitOpsChange {
|
|
|
170
170
|
hookPhase?: string
|
|
171
171
|
hasDesired: boolean
|
|
172
172
|
hasLive: boolean
|
|
173
|
+
missing?: boolean
|
|
173
174
|
// Structured per-field diff between the desired state (parsed from
|
|
174
175
|
// kubectl.kubernetes.io/last-applied-configuration) and the live spec.
|
|
175
176
|
// Undefined when the diff couldn't be computed (no annotation, SSA-applied
|
package/src/types/gitops-tree.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface GitOpsTreeNode {
|
|
|
24
24
|
tool: GitOpsTreeTool
|
|
25
25
|
sync?: SyncStatus
|
|
26
26
|
health?: GitOpsHealthStatus
|
|
27
|
+
// A local lookup confirmed NotFound; independent of controller health.
|
|
28
|
+
missing?: boolean
|
|
27
29
|
// Who assessed `health`. 'controller' is the GitOps controller's own
|
|
28
30
|
// verdict; 'radar' is Radar's read of the live object (topology status, or
|
|
29
31
|
// the issues engine's finding when the controller's verdict isn't
|
package/src/types/gitops.ts
CHANGED
|
@@ -446,9 +446,7 @@ export function argoApplicationSetConditionsToGitOpsStatus(
|
|
|
446
446
|
export function parseFluxInventory(entries: Array<{ id: string; v?: string }>): ManagedResource[] {
|
|
447
447
|
return entries
|
|
448
448
|
.map(entry => {
|
|
449
|
-
// Format: namespace_name_group_kind
|
|
450
|
-
// The challenge is that namespace, name, and group can all contain underscores
|
|
451
|
-
// But the format is always: ns_name_group_kind where kind is the last part
|
|
449
|
+
// Format: namespace_name_group_kind; only the name can hold an underscore.
|
|
452
450
|
const id = entry.id || ''
|
|
453
451
|
const parts = id.split('_')
|
|
454
452
|
|
|
@@ -460,9 +458,11 @@ export function parseFluxInventory(entries: Array<{ id: string; v?: string }>):
|
|
|
460
458
|
// Kind is always last, group is second-to-last
|
|
461
459
|
const kind = parts[parts.length - 1]
|
|
462
460
|
const group = parts[parts.length - 2]
|
|
463
|
-
// Namespace is first, name is everything in between
|
|
461
|
+
// Namespace is first, name is everything in between. Flux writes a colon
|
|
462
|
+
// in an RBAC object's name as a double underscore (system:metrics-server
|
|
463
|
+
// is system__metrics-server) and reads every "__" back as a colon.
|
|
464
464
|
const namespace = parts[0]
|
|
465
|
-
const name = parts.slice(1, -2).join('_')
|
|
465
|
+
const name = parts.slice(1, -2).join('_').replaceAll('__', ':')
|
|
466
466
|
|
|
467
467
|
// Validate required fields
|
|
468
468
|
if (!kind || !name) {
|
package/src/types/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './core'
|
|
2
|
+
export * from './read-coverage'
|
|
2
3
|
export * from './fetch-error'
|
|
3
4
|
export * from './gitops'
|
|
4
5
|
export * from './gitops-tree'
|
|
@@ -6,3 +7,5 @@ export * from './gitops-insights'
|
|
|
6
7
|
export * from './rbac'
|
|
7
8
|
export * from './capacity'
|
|
8
9
|
export * from './policy'
|
|
10
|
+
export * from './read-coverage'
|
|
11
|
+
export * from './resource-coverage'
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Coverage of a Radar read, as Radar's pkg/resourceapi serializes it. Codes
|
|
2
|
+
// come from Radar's pkg/readreason.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A known, finite set of `count` members. `names` lists all of them, sorted,
|
|
6
|
+
* when there are few enough for the response; otherwise it is absent and which
|
|
7
|
+
* members are in the set is unknown.
|
|
8
|
+
*/
|
|
9
|
+
export interface ReadNameSet {
|
|
10
|
+
count: number
|
|
11
|
+
names?: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How far a gap reaches along one axis when it can't list it: `all` of the
|
|
16
|
+
* answer's scope, or an `unknown` part of it. A value a client doesn't know
|
|
17
|
+
* reads as unknown.
|
|
18
|
+
*/
|
|
19
|
+
export type ReadExtent = 'all' | 'unknown' | (string & {})
|
|
20
|
+
|
|
21
|
+
/** A gap's set along one axis: listed like a ReadNameSet, or an extent. */
|
|
22
|
+
export type ReadGapSet<T> = { count: number; names?: T[]; extent?: undefined } | { extent: ReadExtent; count?: undefined; names?: undefined }
|
|
23
|
+
|
|
24
|
+
/** A kind; an omitted group is the core group. */
|
|
25
|
+
export interface ReadKindRef {
|
|
26
|
+
kind: string
|
|
27
|
+
group?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ReadObjectRef {
|
|
31
|
+
kind: string
|
|
32
|
+
group?: string
|
|
33
|
+
namespace?: string
|
|
34
|
+
name: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Part of an answer Radar did not read, and why. A gap covers every
|
|
39
|
+
* combination of its axes. An axis that applies is always present, listed or
|
|
40
|
+
* as an extent; an absent axis doesn't apply to the gap (namespaces on a
|
|
41
|
+
* cluster-scoped kind). Within one answer a namespace is in at most one gap.
|
|
42
|
+
* `action` is what the code calls for, so a code the client doesn't know still
|
|
43
|
+
* says what resolves it.
|
|
44
|
+
*/
|
|
45
|
+
export interface ReadGap {
|
|
46
|
+
code: string
|
|
47
|
+
action?: string
|
|
48
|
+
kinds?: ReadGapSet<ReadKindRef>
|
|
49
|
+
namespaces?: ReadGapSet<string>
|
|
50
|
+
objects?: { count: number; names?: ReadObjectRef[] }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Why an implicitly broad read covers less than everything. */
|
|
54
|
+
export type ReadNarrowing = 'user_access' | 'namespace_pin' | 'namespace_collection' | 'kind_collection' | (string & {})
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A search's coverage: `partial` when something the query named couldn't be
|
|
58
|
+
* searched or a read failed; `narrowedBy` when a broad query searched only
|
|
59
|
+
* what the caller's access and Radar's collection allow.
|
|
60
|
+
*/
|
|
61
|
+
export interface SearchCoverage {
|
|
62
|
+
status: 'complete' | 'partial'
|
|
63
|
+
narrowedBy?: ReadNarrowing[]
|
|
64
|
+
gaps: ReadGap[]
|
|
65
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReadGap, ReadNameSet, ReadNarrowing } from './read-coverage'
|
|
2
|
+
|
|
3
|
+
export interface ResourceReadScope {
|
|
4
|
+
type: 'cluster' | 'all_namespaces' | 'namespaces'
|
|
5
|
+
namespaces?: ReadNameSet
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A single-kind read's coverage. `status` is relative to `scope`: the
|
|
10
|
+
* namespaces the request named, or what remained of every namespace after
|
|
11
|
+
* `narrowedBy` (the caller's access, Radar's pin, what Radar collects) narrowed it.
|
|
12
|
+
*/
|
|
13
|
+
export interface ResourceReadCoverage {
|
|
14
|
+
status: 'complete' | 'partial'
|
|
15
|
+
scope: ResourceReadScope
|
|
16
|
+
narrowedBy?: ReadNarrowing[]
|
|
17
|
+
/** What Radar read; on every successful answer, never on a failed read's coverage. */
|
|
18
|
+
read?: ResourceReadScope
|
|
19
|
+
gaps: ReadGap[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ResourceCoverage = ResourceReadCoverage | { status: 'unknown' }
|
|
23
|
+
|
|
24
|
+
export interface ResourceListResponse<T> {
|
|
25
|
+
items: T[]
|
|
26
|
+
coverage: ResourceReadCoverage
|
|
27
|
+
/** The lowercased plural from the request path, not a Kubernetes Kind. */
|
|
28
|
+
kind?: string
|
|
29
|
+
/** Omitted for the core group. */
|
|
30
|
+
group?: string
|
|
31
|
+
columns?: import('../components/resources/printer-columns').PrinterColumnDef[] | null
|
|
32
|
+
cells?: Record<string, unknown[]> | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A failed resource read's body, on REST and in MCP results alike. */
|
|
36
|
+
export interface ResourceReadErrorBody {
|
|
37
|
+
error: string
|
|
38
|
+
error_code: string
|
|
39
|
+
coverage?: ResourceReadCoverage
|
|
40
|
+
}
|
package/src/utils/format.test.ts
CHANGED
|
@@ -50,6 +50,13 @@ describe('parseMemoryToBytes', () => {
|
|
|
50
50
|
expect(parseMemoryToBytes('1E3')).toBe(1000)
|
|
51
51
|
expect(formatMemoryString('1e9')).toBe('954 MiB')
|
|
52
52
|
})
|
|
53
|
+
|
|
54
|
+
it('reads sub-unit suffixes as fractions of a byte', () => {
|
|
55
|
+
expect(parseMemoryToBytes('1181116006400m')).toBeCloseTo(1181116006.4)
|
|
56
|
+
expect(parseMemoryToBytes('1000000u')).toBeCloseTo(1)
|
|
57
|
+
expect(parseMemoryToBytes('1000000000n')).toBeCloseTo(1)
|
|
58
|
+
expect(formatMemoryString('18360985190400m')).toBe('17.1 GiB')
|
|
59
|
+
})
|
|
53
60
|
})
|
|
54
61
|
|
|
55
62
|
describe('parseQuantityToNumber', () => {
|