@skyhook-io/radar-app 1.16.3 → 1.17.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 +1 -1
- package/package.json +3 -3
- package/src/App.tsx +138 -93
- package/src/RadarApp.tsx +27 -17
- package/src/api/actions.errorCode.test.ts +40 -0
- package/src/api/actions.test.ts +48 -0
- package/src/api/actions.ts +79 -0
- package/src/api/apiResources.navigation.test.tsx +232 -0
- package/src/api/apiResources.ts +46 -3
- package/src/api/client.goneResource.test.ts +49 -0
- package/src/api/client.helmSource.test.ts +39 -0
- package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
- package/src/api/client.namespaceSwitch.test.ts +107 -0
- package/src/api/client.ts +381 -47
- package/src/api/cnpg-ha.live.test.ts +140 -0
- package/src/api/cnpg-ha.test.ts +78 -0
- package/src/api/cnpg-ha.ts +19 -0
- package/src/api/cnpg-history.ts +123 -0
- package/src/api/cnpg-inspect.ts +102 -0
- package/src/api/cnpg-protection.ts +112 -0
- package/src/api/cnpg-recovery.test.tsx +60 -0
- package/src/api/cnpg-recovery.ts +262 -0
- package/src/api/cnpg-sessions.ts +175 -0
- package/src/api/cnpg-storage.ts +138 -0
- package/src/api/cnpg.legacy-reads.test.ts +43 -0
- package/src/api/cnpg.pooler-runtime.test.ts +15 -0
- package/src/api/cnpg.ts +428 -10
- package/src/api/diagnose.test.ts +40 -0
- package/src/api/diagnose.ts +12 -1
- package/src/api/radarFeatures.render.test.tsx +65 -3
- package/src/api/radarFeatures.ts +6 -0
- package/src/api/reviewedActionMutation.test.tsx +164 -0
- package/src/api/reviewedActionMutation.ts +66 -0
- package/src/api/timelineSource.test.ts +9 -0
- package/src/api/timelineSource.ts +8 -0
- package/src/api/traffic.sources.test.ts +55 -0
- package/src/api/traffic.ts +173 -14
- package/src/components/applications/ApplicationsView.tsx +3 -0
- package/src/components/audit/AuditView.tsx +9 -0
- package/src/components/capacity/CapacityActivity.tsx +3 -31
- package/src/components/capacity/CapacityDemand.tsx +4 -36
- package/src/components/capacity/CapacityOverview.tsx +217 -56
- package/src/components/capacity/CapacityPoolDetail.tsx +24 -62
- package/src/components/capacity/CapacityView.test.tsx +354 -1
- package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
- package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
- package/src/components/capacity/routes.ts +42 -0
- package/src/components/capacity/shared.tsx +23 -150
- package/src/components/capacity/workspaceRoute.ts +18 -0
- package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
- package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
- package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
- package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
- package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
- package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
- package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
- package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
- package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
- package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
- package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
- package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
- package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
- package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
- package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
- package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
- package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
- package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
- package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
- package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
- package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
- package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
- package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
- package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
- package/src/components/cnpg/CNPGOperator.tsx +78 -30
- package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
- package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
- package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
- package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
- package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
- package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
- package/src/components/cnpg/CNPGOverview.tsx +238 -63
- package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
- package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
- package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
- package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
- package/src/components/cnpg/CNPGPerformance.tsx +477 -0
- package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
- package/src/components/cnpg/CNPGPooling.tsx +97 -40
- package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
- package/src/components/cnpg/CNPGProtection.tsx +230 -124
- package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
- package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
- package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
- package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
- package/src/components/cnpg/CNPGStorage.tsx +619 -0
- package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
- package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
- package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
- package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
- package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
- package/src/components/cnpg/CNPGTrends.test.ts +38 -0
- package/src/components/cnpg/CNPGTrends.tsx +702 -0
- package/src/components/cnpg/CNPGView.tsx +34 -21
- package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
- package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
- package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
- package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
- package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
- package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
- package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
- package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
- package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
- package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
- package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
- package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
- package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
- package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
- package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
- package/src/components/cnpg/actions/actionModel.test.ts +111 -0
- package/src/components/cnpg/actions/actionModel.ts +127 -0
- package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
- package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
- package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
- package/src/components/cnpg/archivingRepair.test.ts +104 -0
- package/src/components/cnpg/archivingRepair.ts +145 -0
- package/src/components/cnpg/baseBackup.test.ts +36 -0
- package/src/components/cnpg/baseBackup.ts +36 -0
- package/src/components/cnpg/blocking.test.ts +59 -0
- package/src/components/cnpg/blocking.ts +131 -0
- package/src/components/cnpg/coverageText.test.ts +27 -0
- package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
- package/src/components/cnpg/creation/targetModel.test.ts +137 -0
- package/src/components/cnpg/creation/targetModel.ts +149 -0
- package/src/components/cnpg/fleetStatus.test.ts +46 -0
- package/src/components/cnpg/fleetStatus.ts +43 -0
- package/src/components/cnpg/formFields.ts +2 -0
- package/src/components/cnpg/grantText.test.tsx +11 -0
- package/src/components/cnpg/host.test.tsx +24 -0
- package/src/components/cnpg/host.tsx +101 -0
- package/src/components/cnpg/inspectModel.test.ts +120 -0
- package/src/components/cnpg/inspectModel.ts +140 -0
- package/src/components/cnpg/logicalSlots.test.ts +30 -0
- package/src/components/cnpg/logicalSlots.ts +29 -0
- package/src/components/cnpg/lsn.test.ts +40 -0
- package/src/components/cnpg/lsn.ts +43 -0
- package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
- package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
- package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
- package/src/components/cnpg/operations/model.test.ts +510 -0
- package/src/components/cnpg/operations/model.ts +499 -0
- package/src/components/cnpg/operations/presentation.test.ts +80 -0
- package/src/components/cnpg/operations/presentation.ts +84 -0
- package/src/components/cnpg/operations/store.ts +98 -0
- package/src/components/cnpg/operatorStatus.test.ts +134 -0
- package/src/components/cnpg/operatorStatus.ts +194 -0
- package/src/components/cnpg/paths.test.ts +54 -0
- package/src/components/cnpg/paths.ts +47 -4
- package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
- package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
- package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
- package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
- package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
- package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
- package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
- package/src/components/cnpg/protection/scheduleModel.ts +16 -0
- package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
- package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
- package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
- package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
- package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
- package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
- package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
- package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
- package/src/components/cnpg/recovery/restoreModel.ts +685 -0
- package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
- package/src/components/cnpg/refresh.test.ts +38 -0
- package/src/components/cnpg/refresh.ts +17 -0
- package/src/components/cnpg/refreshNotice.test.tsx +29 -0
- package/src/components/cnpg/routes.test.ts +34 -3
- package/src/components/cnpg/routes.ts +60 -48
- package/src/components/cnpg/runtimeAssessment.ts +198 -0
- package/src/components/cnpg/runtimeModel.test.ts +175 -0
- package/src/components/cnpg/runtimeModel.ts +196 -0
- package/src/components/cnpg/runtimeParts.test.tsx +17 -0
- package/src/components/cnpg/runtimeParts.tsx +79 -0
- package/src/components/cnpg/shared.test.tsx +59 -0
- package/src/components/cnpg/shared.tsx +100 -182
- package/src/components/cnpg/storageModel.test.ts +107 -0
- package/src/components/cnpg/storageModel.ts +101 -0
- package/src/components/cnpg/trendSamples.test.ts +148 -0
- package/src/components/cnpg/trendSamples.ts +222 -0
- package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
- package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
- package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
- package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
- package/src/components/cnpg/useCNPGNavigate.ts +21 -0
- package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
- package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
- package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
- package/src/components/cnpg/workspaceRoute.ts +23 -0
- package/src/components/cost/ApplicationCostTab.test.ts +7 -0
- package/src/components/cost/ApplicationCostTab.tsx +7 -7
- package/src/components/cost/CostView.test.ts +2 -1
- package/src/components/cost/CostView.tsx +0 -2
- package/src/components/cost/WorkloadCostTab.tsx +2 -4
- package/src/components/cost/errors.ts +3 -1
- package/src/components/cost/source.test.ts +10 -0
- package/src/components/cost/source.ts +5 -1
- package/src/components/diagnose/AgentSetupNotice.test.tsx +58 -0
- package/src/components/diagnose/AgentSetupNotice.tsx +142 -51
- package/src/components/diagnose/ApplyDialog.ack.test.tsx +47 -0
- package/src/components/diagnose/ApplyDialog.test.tsx +59 -6
- package/src/components/diagnose/ApplyDialog.tsx +26 -28
- package/src/components/diagnose/DiagnoseContext.test.ts +43 -0
- package/src/components/diagnose/DiagnoseContext.tsx +136 -54
- package/src/components/diagnose/DiagnoseSurface.tsx +16 -3
- package/src/components/diagnose/InvestigationView.tsx +39 -2
- package/src/components/diagnose/LocalDiagnoseAction.tsx +40 -20
- package/src/components/diagnose/agentCatalog.ts +2 -2
- package/src/components/diagnose/diagnoseEvidenceTypes.ts +2 -0
- package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
- package/src/components/dock/BottomDock.tsx +2 -0
- package/src/components/dock/TerminalTab.test.tsx +17 -0
- package/src/components/dock/TerminalTab.tsx +7 -3
- package/src/components/dock/TrafficFlowListTab.tsx +27 -6
- package/src/components/execution/BatchExecutionView.tsx +11 -11
- package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
- package/src/components/gitops/GitOpsView.tsx +16 -23
- package/src/components/helm/ChartBrowser.tsx +3 -3
- package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
- package/src/components/helm/HelmCompareRoute.tsx +57 -26
- package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
- package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
- package/src/components/helm/RevisionHistory.tsx +3 -2
- package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
- package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
- package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
- package/src/components/helm/ValuesViewer.test.tsx +301 -0
- package/src/components/helm/ValuesViewer.tsx +169 -78
- package/src/components/home/ActivitySummary.tsx +2 -1
- package/src/components/home/ClusterHealthCard.tsx +7 -0
- package/src/components/home/HomeView.tsx +20 -9
- package/src/components/home/ProblemsPanel.test.tsx +81 -0
- package/src/components/home/TopologyPreview.tsx +2 -2
- package/src/components/home/mcpToolCatalog.ts +1 -1
- package/src/components/issues/IssuesPane.render.test.tsx +122 -0
- package/src/components/issues/IssuesPane.test.ts +65 -1
- package/src/components/issues/IssuesPane.tsx +188 -25
- package/src/components/issues/uncheckedSources.ts +21 -0
- package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
- package/src/components/resource/PVCUsageBar.test.tsx +24 -0
- package/src/components/resource/PVCUsageBar.tsx +15 -6
- package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
- package/src/components/resources/CompositeRenderer.tsx +4 -4
- package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
- package/src/components/resources/ResourcesView.tsx +26 -12
- package/src/components/resources/renderers/KyvernoPolicyQueued.tsx +2 -1
- package/src/components/resources/renderers/NodeRenderer.tsx +7 -1
- package/src/components/resources/renderers/ServiceRenderer.test.tsx +81 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +19 -14
- package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
- package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
- package/src/components/settings/SettingsDialog.tsx +150 -34
- package/src/components/shared/LargeClusterNamespacePicker.tsx +11 -5
- package/src/components/timeline/TimelineList.tsx +25 -24
- package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
- package/src/components/timeline/TimelineLoadError.tsx +96 -0
- package/src/components/timeline/TimelineRetentionNote.test.tsx +49 -0
- package/src/components/timeline/TimelineRetentionNote.tsx +20 -0
- package/src/components/timeline/TimelineView.tsx +25 -45
- package/src/components/timeline/ViewModeToggle.tsx +20 -0
- package/src/components/timeline/useStickyError.test.tsx +47 -0
- package/src/components/timeline/useStickyError.ts +15 -0
- package/src/components/traffic/TrafficConnectionsTable.tsx +144 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +22 -1
- package/src/components/traffic/TrafficFlowListContext.tsx +12 -4
- package/src/components/traffic/TrafficFocusSearch.tsx +172 -0
- package/src/components/traffic/TrafficGraph.tsx +109 -26
- package/src/components/traffic/TrafficGraphTooLarge.tsx +150 -0
- package/src/components/traffic/TrafficView.tsx +582 -149
- package/src/components/traffic/trafficFilters.test.ts +398 -1
- package/src/components/traffic/trafficFilters.ts +408 -0
- package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
- package/src/components/ui/Markdown.test.tsx +65 -0
- package/src/components/ui/Markdown.tsx +144 -127
- package/src/components/whats-new/releaseNotes.ts +63 -1
- package/src/components/workload/WorkloadView.test.ts +11 -1
- package/src/components/workload/WorkloadView.tsx +162 -529
- package/src/components/workspace/layout.test.tsx +29 -0
- package/src/components/workspace/layout.tsx +259 -0
- package/src/components/workspace/table.ts +7 -0
- package/src/context/TimelineCustomization.tsx +15 -0
- package/src/contexts/CapabilitiesContext.tsx +1 -1
- package/src/hooks/useGitOpsWriteGuard.test.tsx +457 -0
- package/src/hooks/useGitOpsWriteGuard.ts +193 -0
- package/src/hooks/useResolvedGitOpsOwner.ts +256 -0
- package/src/integrations/resourceHost.test.tsx +55 -0
- package/src/integrations/resourceHost.ts +99 -0
- package/src/integrations/resourceHosts.test.tsx +141 -0
- package/src/integrations/resourceHosts.tsx +119 -0
- package/src/integrations/workspaceRoute.ts +20 -0
- package/src/integrations/workspaceRoutes.test.ts +40 -0
- package/src/integrations/workspaceRoutes.ts +22 -0
- package/src/integrations/workspaceScreens.tsx +14 -0
- package/src/utils/drawer-trail.ts +35 -0
- package/src/utils/kindFilterStore.test.ts +147 -0
- package/src/utils/kindFilterStore.ts +91 -0
- package/src/utils/navigation.test.ts +13 -1
- package/src/utils/navigation.ts +39 -2
- package/src/utils/page-links.ts +21 -0
- package/src/utils/tabStorage.ts +40 -0
- package/src/utils/viewMemory.test.ts +78 -0
- package/src/utils/viewMemory.ts +54 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { expect, it, vi } from 'vitest'
|
|
5
|
+
import { Mono } from './layout'
|
|
6
|
+
|
|
7
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
8
|
+
|
|
9
|
+
it('uses normal word boundaries with overflow wrapping and keeps the complete value in a hover tooltip', () => {
|
|
10
|
+
vi.useFakeTimers()
|
|
11
|
+
const host = document.createElement('div')
|
|
12
|
+
document.body.append(host)
|
|
13
|
+
const root = createRoot(host)
|
|
14
|
+
const image = 'ghcr.io/cloudnative-pg/postgresql:17.4-standard-bookworm'
|
|
15
|
+
act(() => root.render(<Mono>{image}</Mono>))
|
|
16
|
+
const mono = host.querySelector('.font-mono')!
|
|
17
|
+
expect(mono.classList.contains('break-all')).toBe(false)
|
|
18
|
+
expect(mono.classList.contains('break-normal')).toBe(true)
|
|
19
|
+
expect(mono.textContent).toBe(image)
|
|
20
|
+
expect(mono.classList.contains('[overflow-wrap:anywhere]')).toBe(true)
|
|
21
|
+
const trigger = mono.parentElement!
|
|
22
|
+
act(() => trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })))
|
|
23
|
+
act(() => trigger.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })))
|
|
24
|
+
act(() => vi.advanceTimersByTime(500))
|
|
25
|
+
expect(document.body.querySelector('.fixed')?.textContent).toBe(image)
|
|
26
|
+
act(() => root.unmount())
|
|
27
|
+
host.remove()
|
|
28
|
+
vi.useRealTimers()
|
|
29
|
+
})
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react'
|
|
2
|
+
import type { UseQueryResult } from '@tanstack/react-query'
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import { AlertTriangle, X } from 'lucide-react'
|
|
5
|
+
import { Tooltip, formatUpdatedAgo, grantParts, toneTextClass, type Grant } from '@skyhook-io/k8s-ui'
|
|
6
|
+
import type { SelectedResource } from '../../types'
|
|
7
|
+
import { sameSelectedResource } from '../../utils/drawer-trail'
|
|
8
|
+
import { ROW_HOVER, TABLE_HEAD, TABLE_WRAP, TBODY, TD, TH } from './table'
|
|
9
|
+
|
|
10
|
+
export function Notice({ children }: { children: ReactNode }) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex items-start gap-2 rounded-lg border border-theme-border bg-theme-surface px-3 py-2 text-sm text-theme-text-secondary">
|
|
13
|
+
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-theme-text-tertiary" />
|
|
14
|
+
<div>{children}</div>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A whole screen with nothing to show: not installed, unreadable, or empty. */
|
|
20
|
+
export function ScreenEmptyState({
|
|
21
|
+
icon: Icon,
|
|
22
|
+
title,
|
|
23
|
+
detail,
|
|
24
|
+
action,
|
|
25
|
+
}: {
|
|
26
|
+
icon: ComponentType<{ className?: string }>;
|
|
27
|
+
title: string;
|
|
28
|
+
detail: ReactNode;
|
|
29
|
+
action?: ReactNode;
|
|
30
|
+
}) {
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex min-h-0 flex-1 items-center justify-center bg-theme-base p-6">
|
|
33
|
+
<div className="max-w-md text-center">
|
|
34
|
+
<Icon className="mx-auto h-9 w-9 text-theme-text-tertiary/50" />
|
|
35
|
+
<h2 className="mt-3 text-lg font-medium text-theme-text-primary">
|
|
36
|
+
{title}
|
|
37
|
+
</h2>
|
|
38
|
+
<p className="mt-1 text-sm text-theme-text-secondary">{detail}</p>
|
|
39
|
+
{action}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ScreenBody({ children }: { children: ReactNode }) {
|
|
46
|
+
return (
|
|
47
|
+
<div className="min-h-0 flex-1 overflow-y-auto [scrollbar-gutter:stable]">
|
|
48
|
+
<div className="space-y-5 px-5 pb-6 pt-3 xl:px-7">{children}</div>
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function FilterChips({ chips }: { chips: { label: string; onClear: () => void }[] }) {
|
|
54
|
+
if (chips.length === 0) return null
|
|
55
|
+
return (
|
|
56
|
+
<div className="flex flex-wrap gap-1.5">
|
|
57
|
+
{chips.map((c) => (
|
|
58
|
+
<span key={c.label} className="inline-flex items-center gap-1 rounded-full bg-theme-elevated px-2.5 py-0.5 text-xs text-theme-text-secondary">
|
|
59
|
+
{c.label}
|
|
60
|
+
<button type="button" onClick={c.onClear} aria-label={`Remove ${c.label}`} className="rounded-full p-0.5 hover:bg-theme-hover">
|
|
61
|
+
<X className="h-3 w-3" />
|
|
62
|
+
</button>
|
|
63
|
+
</span>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function namespaceChip(namespaces: string[], onClear: () => void) {
|
|
70
|
+
return namespaces.length > 0 ? [{ label: `Namespace: ${namespaces.join(', ')}`, onClear }] : []
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function Segments<T extends string>({
|
|
74
|
+
value,
|
|
75
|
+
options,
|
|
76
|
+
onChange,
|
|
77
|
+
label,
|
|
78
|
+
}: {
|
|
79
|
+
value: T
|
|
80
|
+
options: { id: T; label: string; count?: number | string }[]
|
|
81
|
+
onChange: (id: T) => void
|
|
82
|
+
label: string
|
|
83
|
+
}) {
|
|
84
|
+
return (
|
|
85
|
+
<div role="tablist" aria-label={label} className="inline-flex rounded-lg bg-theme-elevated p-0.5">
|
|
86
|
+
{options.map((o) => {
|
|
87
|
+
const on = o.id === value
|
|
88
|
+
return (
|
|
89
|
+
<button
|
|
90
|
+
key={o.id}
|
|
91
|
+
type="button"
|
|
92
|
+
role="tab"
|
|
93
|
+
aria-selected={on}
|
|
94
|
+
onClick={() => onChange(o.id)}
|
|
95
|
+
className={clsx(
|
|
96
|
+
'inline-flex items-center gap-1.5 rounded-md px-3 py-1 text-sm font-medium transition-colors',
|
|
97
|
+
on ? 'bg-theme-surface text-theme-text-primary shadow-theme-sm' : 'text-theme-text-secondary hover:text-theme-text-primary',
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
{o.label}
|
|
101
|
+
{o.count !== undefined && <span className="font-mono text-xs text-theme-text-tertiary">{o.count}</span>}
|
|
102
|
+
</button>
|
|
103
|
+
)
|
|
104
|
+
})}
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface TableColumn<T> {
|
|
110
|
+
header: ReactNode
|
|
111
|
+
width?: string
|
|
112
|
+
cell: (row: T) => ReactNode
|
|
113
|
+
className?: string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A workspace table. Rows inspect in the drawer; the inspected row is highlighted. */
|
|
117
|
+
export function SectionTable<T>({
|
|
118
|
+
title,
|
|
119
|
+
subtitle,
|
|
120
|
+
columns,
|
|
121
|
+
rows,
|
|
122
|
+
rowKey,
|
|
123
|
+
rowResource,
|
|
124
|
+
onInspect,
|
|
125
|
+
inspected,
|
|
126
|
+
empty,
|
|
127
|
+
minWidth = 760,
|
|
128
|
+
footer,
|
|
129
|
+
}: {
|
|
130
|
+
title: ReactNode
|
|
131
|
+
subtitle?: ReactNode
|
|
132
|
+
columns: TableColumn<T>[]
|
|
133
|
+
rows: T[]
|
|
134
|
+
rowKey: (row: T) => string
|
|
135
|
+
rowResource?: (row: T) => SelectedResource | null
|
|
136
|
+
onInspect?: (resource: SelectedResource) => void
|
|
137
|
+
inspected?: SelectedResource | null
|
|
138
|
+
empty: ReactNode
|
|
139
|
+
minWidth?: number
|
|
140
|
+
footer?: ReactNode
|
|
141
|
+
}) {
|
|
142
|
+
return (
|
|
143
|
+
<section>
|
|
144
|
+
<div className="mb-2 flex flex-wrap items-baseline gap-x-2.5 gap-y-1">
|
|
145
|
+
<h2 className="text-sm font-semibold text-theme-text-primary">{title}</h2>
|
|
146
|
+
{subtitle && <span className="text-xs text-theme-text-tertiary">{subtitle}</span>}
|
|
147
|
+
</div>
|
|
148
|
+
<div className="overflow-hidden rounded-xl border border-theme-border bg-theme-surface shadow-theme-sm">
|
|
149
|
+
{rows.length === 0 ? (
|
|
150
|
+
<div className="px-4 py-5 text-sm text-theme-text-tertiary">{empty}</div>
|
|
151
|
+
) : (
|
|
152
|
+
<div className={TABLE_WRAP}>
|
|
153
|
+
<table className="w-full table-fixed" style={{ minWidth }}>
|
|
154
|
+
<colgroup>
|
|
155
|
+
{columns.map((c, i) => (
|
|
156
|
+
<col key={i} style={c.width ? { width: c.width } : undefined} />
|
|
157
|
+
))}
|
|
158
|
+
</colgroup>
|
|
159
|
+
<thead className={TABLE_HEAD}>
|
|
160
|
+
<tr>
|
|
161
|
+
{columns.map((c, i) => (
|
|
162
|
+
<th key={i} className={TH}>{c.header}</th>
|
|
163
|
+
))}
|
|
164
|
+
</tr>
|
|
165
|
+
</thead>
|
|
166
|
+
<tbody className={TBODY}>
|
|
167
|
+
{rows.map((row) => {
|
|
168
|
+
const res = rowResource?.(row) ?? null
|
|
169
|
+
const active = !!res && sameSelectedResource(inspected, res)
|
|
170
|
+
return (
|
|
171
|
+
<tr
|
|
172
|
+
key={rowKey(row)}
|
|
173
|
+
onClick={res && onInspect ? () => onInspect(res) : undefined}
|
|
174
|
+
className={clsx(res && onInspect && 'cursor-pointer', ROW_HOVER, active && 'selection')}
|
|
175
|
+
aria-selected={res ? active : undefined}
|
|
176
|
+
>
|
|
177
|
+
{columns.map((c, i) => (
|
|
178
|
+
<td key={i} className={clsx(TD, c.className)}>{c.cell(row)}</td>
|
|
179
|
+
))}
|
|
180
|
+
</tr>
|
|
181
|
+
)
|
|
182
|
+
})}
|
|
183
|
+
</tbody>
|
|
184
|
+
</table>
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
{footer && <div className="mt-1.5 text-xs text-theme-text-tertiary">{footer}</div>}
|
|
189
|
+
</section>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Text that wraps only after `after` ("/" for a path, "-" for a resource
|
|
195
|
+
* name), never mid-word; a single segment too long for its cell is cut with an
|
|
196
|
+
* ellipsis. The whole value shows on hover.
|
|
197
|
+
*/
|
|
198
|
+
export function BreakText({ value, after, className }: { value: string; after: '/' | '-'; className?: string }) {
|
|
199
|
+
const parts = value.split(after === '/' ? /(?<=\/)/ : /(?<=-)/)
|
|
200
|
+
return (
|
|
201
|
+
<Tooltip content={value} wrapperClassName="max-w-full">
|
|
202
|
+
<span className={clsx('block max-w-full', className)}>
|
|
203
|
+
{parts.map((p, i) => (
|
|
204
|
+
<span key={i} className="inline-block max-w-full truncate align-top">
|
|
205
|
+
{p}
|
|
206
|
+
</span>
|
|
207
|
+
))}
|
|
208
|
+
</span>
|
|
209
|
+
</Tooltip>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A URL or path; see BreakText. */
|
|
214
|
+
export function PathText({ value, className }: { value: string; className?: string }) {
|
|
215
|
+
return <BreakText value={value} after="/" className={clsx('font-mono text-[12.5px]', className)} />
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* A grant as one unit: the verb and resource in a code span that does not
|
|
220
|
+
* wrap, its scope ("cluster-wide", "in namespace pg") as plain text after it.
|
|
221
|
+
*/
|
|
222
|
+
export function GrantText({ grant }: { grant: Grant }) {
|
|
223
|
+
const { what, scope } = grantParts(grant)
|
|
224
|
+
return (
|
|
225
|
+
<>
|
|
226
|
+
<code className="whitespace-nowrap rounded bg-theme-elevated px-1 font-mono text-[12px]">{what}</code>
|
|
227
|
+
{scope}
|
|
228
|
+
</>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function Mono({ children }: { children: ReactNode }) {
|
|
233
|
+
return <Tooltip content={children} wrapperClassName="min-w-0"><span className="font-mono text-[12.5px] break-normal [overflow-wrap:anywhere]">{children}</span></Tooltip>
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function Sub({ children }: { children: ReactNode }) {
|
|
237
|
+
return <div className="mt-0.5 text-xs text-theme-text-tertiary break-words">{children}</div>
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
type RefreshableQuery = Pick<UseQueryResult<unknown>, 'isRefetchError' | 'error' | 'dataUpdatedAt'>
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* A refetch failed while the last good answer stays on screen: say so, why,
|
|
244
|
+
* and how old that answer is, so cached values are not read as current.
|
|
245
|
+
*/
|
|
246
|
+
export function RefreshFailedNotice({ queries, className }: { queries: RefreshableQuery[]; className?: string }) {
|
|
247
|
+
const failed = queries.filter((q) => q.isRefetchError)
|
|
248
|
+
if (failed.length === 0) return null
|
|
249
|
+
const oldest = failed.reduce((a, b) => (b.dataUpdatedAt < a.dataUpdatedAt ? b : a))
|
|
250
|
+
const reason = oldest.error instanceof Error ? oldest.error.message : 'unknown error'
|
|
251
|
+
return (
|
|
252
|
+
<div role="status" className={clsx('flex items-start gap-1.5 text-xs text-theme-text-secondary', className)}>
|
|
253
|
+
<AlertTriangle className={clsx('mt-px h-3.5 w-3.5 shrink-0', toneTextClass('degraded'))} />
|
|
254
|
+
<span>
|
|
255
|
+
Last refresh failed: {reason.length > 160 ? `${reason.slice(0, 160)}…` : reason} · showing data from {formatUpdatedAgo(Date.now() - oldest.dataUpdatedAt)}
|
|
256
|
+
</span>
|
|
257
|
+
</div>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const TABLE_WRAP = 'overflow-x-auto';
|
|
2
|
+
export const TABLE_HEAD =
|
|
3
|
+
'border-b border-theme-border bg-theme-base/60 text-[11px] uppercase tracking-wide text-theme-text-tertiary';
|
|
4
|
+
export const TH = 'px-3 py-2.5 text-left font-medium whitespace-nowrap';
|
|
5
|
+
export const TD = 'px-3 py-2.5 align-top text-sm text-theme-text-primary';
|
|
6
|
+
export const TBODY = 'table-divide-subtle';
|
|
7
|
+
export const ROW_HOVER = 'transition-colors hover:bg-theme-hover/50';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A note from the host about how far back the timeline goes. The host owns
|
|
6
|
+
* the wording and decides when there is one. Standalone Radar serves
|
|
7
|
+
* everything it holds and never sets it.
|
|
8
|
+
*/
|
|
9
|
+
const TimelineRetentionNoteContext = createContext<ReactNode>(undefined)
|
|
10
|
+
|
|
11
|
+
export const TimelineRetentionNoteProvider = TimelineRetentionNoteContext.Provider
|
|
12
|
+
|
|
13
|
+
export function useTimelineRetentionNote(): ReactNode {
|
|
14
|
+
return useContext(TimelineRetentionNoteContext)
|
|
15
|
+
}
|
|
@@ -155,7 +155,7 @@ export function useNamespacedCapabilities(namespace: string | undefined) {
|
|
|
155
155
|
const { data: nsCaps, error, isPending } = useNamespaceCapabilities(namespace, globalCaps)
|
|
156
156
|
|
|
157
157
|
if (error) {
|
|
158
|
-
console.warn(
|
|
158
|
+
console.warn('Failed to fetch namespace capabilities, using global:', namespace, error)
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
return useMemo(() => ({
|