@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
|
@@ -23,7 +23,7 @@ import { AISettingsSection, type AIDraft } from '../diagnose/AISettings'
|
|
|
23
23
|
import { MyPermissionsContent } from './MyPermissionsDialog'
|
|
24
24
|
import { PrivacySection } from './PrivacySection'
|
|
25
25
|
import { ConfigToggle, SubHeading } from './controls'
|
|
26
|
-
import { useDiagnose } from '../diagnose/DiagnoseContext'
|
|
26
|
+
import { useDiagnose, type DiagnoseSetup } from '../diagnose/DiagnoseContext'
|
|
27
27
|
import { currencyOptionsForValue } from './currency-options'
|
|
28
28
|
import { UpdateNotification } from '../ui/UpdateNotification'
|
|
29
29
|
import {
|
|
@@ -856,11 +856,8 @@ export function SettingsDialog({
|
|
|
856
856
|
anonymous={argoSectionStatus?.connected && argoSectionStatus.anonymous}
|
|
857
857
|
connectedAddress={argoSectionStatus?.connected && !argoSectionStatus.anonymous ? argoSectionStatus.address : undefined}
|
|
858
858
|
active={section === 'argocd'}
|
|
859
|
-
statusReason={
|
|
860
|
-
|
|
861
|
-
? argoSectionStatus.reason
|
|
862
|
-
: undefined
|
|
863
|
-
}
|
|
859
|
+
statusReason={argoSectionStatus?.configured ? argoSectionStatus.reason : undefined}
|
|
860
|
+
connecting={argoSectionStatus?.connecting}
|
|
864
861
|
onChangeUrl={(v) => updateConfigField('argoCdUrl', v || undefined)}
|
|
865
862
|
onChangeInsecureTls={(v) => updateConfigField('argoCdInsecureTls', v || undefined)}
|
|
866
863
|
onApplied={({ url, insecureTls, tokenSet }) => {
|
|
@@ -924,7 +921,13 @@ export function SettingsDialog({
|
|
|
924
921
|
)}
|
|
925
922
|
</div>
|
|
926
923
|
) : (
|
|
927
|
-
<AIUnavailableNotice
|
|
924
|
+
<AIUnavailableNotice
|
|
925
|
+
setupState={diag.setupState}
|
|
926
|
+
cliOverride={diag.cliOverride}
|
|
927
|
+
checkingAgents={diag.checkingAgents}
|
|
928
|
+
agentsCheckFailed={diag.agentsCheckFailed}
|
|
929
|
+
recheckAgents={diag.recheckAgents}
|
|
930
|
+
/>
|
|
928
931
|
)}
|
|
929
932
|
</div>
|
|
930
933
|
|
|
@@ -1212,7 +1215,7 @@ function OperatorSettingsSummary({ section, config }: { section: SettingsSection
|
|
|
1212
1215
|
break
|
|
1213
1216
|
case 'argocd':
|
|
1214
1217
|
rows = [
|
|
1215
|
-
['Status', argoError ? 'Status unavailable' : !argo ? 'Checking…' : argo.connected ? 'Connected' : 'Not connected'],
|
|
1218
|
+
['Status', argoError ? 'Status unavailable' : !argo ? 'Checking…' : argo.connected ? 'Connected' : argo.connecting ? 'Connecting…' : 'Not connected'],
|
|
1216
1219
|
['Endpoint', argo?.address || c.argoCdUrl || 'Auto-discovery'],
|
|
1217
1220
|
['Token', config.argoCdTokenSet ? 'Configured' : 'None configured'],
|
|
1218
1221
|
['TLS verification', c.argoCdInsecureTls ? 'Disabled' : 'Enabled'],
|
|
@@ -1311,7 +1314,7 @@ function OverviewPanel({ active, onNavigate }: { active: boolean; onNavigate: (s
|
|
|
1311
1314
|
: cost?.reason === 'access_denied'
|
|
1312
1315
|
? 'Your current permissions do not allow cost data.'
|
|
1313
1316
|
: cost?.reason
|
|
1314
|
-
? costIntegrationUnavailableMessage(cost.reason) ?? 'The active cost source
|
|
1317
|
+
? costIntegrationUnavailableMessage(cost.reason) ?? 'The active cost source could not provide data.'
|
|
1315
1318
|
: undefined
|
|
1316
1319
|
|
|
1317
1320
|
const rows: OverviewRow[] = [
|
|
@@ -1341,9 +1344,9 @@ function OverviewPanel({ active, onNavigate }: { active: boolean; onNavigate: (s
|
|
|
1341
1344
|
},
|
|
1342
1345
|
{
|
|
1343
1346
|
id: 'argocd', icon: GitBranch, label: 'Argo CD',
|
|
1344
|
-
tone: argo?.connected ? 'ok' : argo?.configured || argo?.reason ? 'warn' : 'off',
|
|
1345
|
-
value: argo?.connected ? (argo.anonymous ? 'Connected · no token needed' : 'Connected') : 'Not connected',
|
|
1346
|
-
detail: argo?.
|
|
1347
|
+
tone: argo?.connected ? (argo.reason ? 'warn' : 'ok') : argo?.connecting ? 'unknown' : argo?.configured || argo?.reason ? 'warn' : 'off',
|
|
1348
|
+
value: argo?.connected ? (argo.anonymous ? 'Connected · no token needed' : 'Connected') : argo?.connecting ? 'Connecting…' : 'Not connected',
|
|
1349
|
+
detail: argo?.reason ? 'Open Argo CD for connection details.' : argo?.connected ? argo.address : undefined,
|
|
1347
1350
|
},
|
|
1348
1351
|
{
|
|
1349
1352
|
id: 'advanced', icon: Zap, label: 'MCP',
|
|
@@ -1354,8 +1357,7 @@ function OverviewPanel({ active, onNavigate }: { active: boolean; onNavigate: (s
|
|
|
1354
1357
|
},
|
|
1355
1358
|
{
|
|
1356
1359
|
id: 'ai', icon: Sparkles, label: 'AI investigations',
|
|
1357
|
-
|
|
1358
|
-
value: aiAvailable ? 'Ready' : 'No agent CLI',
|
|
1360
|
+
...aiOverviewStatus(aiAvailable, diag.setupState, diag.cliOverride, diag.agentsCheckFailed),
|
|
1359
1361
|
detail: aiAvailable ? agentLabel : undefined,
|
|
1360
1362
|
},
|
|
1361
1363
|
]
|
|
@@ -1408,6 +1410,33 @@ function OverviewPanel({ active, onNavigate }: { active: boolean; onNavigate: (s
|
|
|
1408
1410
|
)
|
|
1409
1411
|
}
|
|
1410
1412
|
|
|
1413
|
+
// aiOverviewStatus says which reason applies, for the same reason
|
|
1414
|
+
// AIUnavailableNotice does: "No agent CLI" is a claim about the user's machine,
|
|
1415
|
+
// and it is false when RADAR_AI_CLI_BIN is what's broken, when an agent is
|
|
1416
|
+
// reported but off, when this deployment can't run investigations at all, or
|
|
1417
|
+
// when the agents probe hasn't answered.
|
|
1418
|
+
function aiOverviewStatus(
|
|
1419
|
+
aiAvailable: boolean,
|
|
1420
|
+
setupState: DiagnoseSetup,
|
|
1421
|
+
cliOverride: boolean,
|
|
1422
|
+
checkFailed: boolean,
|
|
1423
|
+
): Pick<OverviewRow, 'tone' | 'value'> {
|
|
1424
|
+
if (aiAvailable) return { tone: 'ok', value: 'Ready' }
|
|
1425
|
+
if (cliOverride && (setupState === 'needs-install' || setupState === 'needs-restart')) {
|
|
1426
|
+
return { tone: 'warn', value: "RADAR_AI_CLI_BIN can't be run" }
|
|
1427
|
+
}
|
|
1428
|
+
switch (setupState) {
|
|
1429
|
+
case 'unknown':
|
|
1430
|
+
return checkFailed ? { tone: 'warn', value: "Couldn't check" } : { tone: 'unknown', value: 'Checking…' }
|
|
1431
|
+
case 'needs-restart':
|
|
1432
|
+
return { tone: 'warn', value: 'Not available right now' }
|
|
1433
|
+
case 'off':
|
|
1434
|
+
return { tone: 'off', value: 'Not available in this deployment' }
|
|
1435
|
+
default:
|
|
1436
|
+
return { tone: 'off', value: 'No agent CLI' }
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1411
1440
|
function OverviewStatus({ tone }: { tone: OverviewTone }) {
|
|
1412
1441
|
const cls =
|
|
1413
1442
|
tone === 'ok' ? 'bg-green-500'
|
|
@@ -1417,24 +1446,95 @@ function OverviewStatus({ tone }: { tone: OverviewTone }) {
|
|
|
1417
1446
|
return <span className={clsx('w-2 h-2 rounded-full shrink-0', cls)} />
|
|
1418
1447
|
}
|
|
1419
1448
|
|
|
1420
|
-
// AIUnavailableNotice is the body of the AI investigations tab when
|
|
1421
|
-
//
|
|
1422
|
-
// this is just the
|
|
1423
|
-
//
|
|
1424
|
-
|
|
1425
|
-
|
|
1449
|
+
// AIUnavailableNotice is the body of the AI investigations tab when the agent,
|
|
1450
|
+
// model and effort controls have nothing to configure. The tab supplies the
|
|
1451
|
+
// heading, so this is just the explainer.
|
|
1452
|
+
//
|
|
1453
|
+
// It must say WHICH of the reasons applies. "No supported agent CLI found" is a
|
|
1454
|
+
// claim about the user's machine, and when a CLI was found but investigations
|
|
1455
|
+
// didn't start it is false.
|
|
1456
|
+
function AIUnavailableNotice({
|
|
1457
|
+
setupState,
|
|
1458
|
+
cliOverride,
|
|
1459
|
+
checkingAgents,
|
|
1460
|
+
agentsCheckFailed,
|
|
1461
|
+
recheckAgents,
|
|
1462
|
+
}: {
|
|
1463
|
+
setupState: DiagnoseSetup
|
|
1464
|
+
cliOverride: boolean
|
|
1465
|
+
checkingAgents: boolean
|
|
1466
|
+
agentsCheckFailed: boolean
|
|
1467
|
+
recheckAgents: () => Promise<void>
|
|
1468
|
+
}) {
|
|
1469
|
+
// The state the tab was in when a check started. "Still…" is only true when
|
|
1470
|
+
// the finished check left it there.
|
|
1471
|
+
const [checkedState, setCheckedState] = useState<DiagnoseSetup | null>(null)
|
|
1472
|
+
const unchanged = checkedState === setupState && !checkingAgents && !agentsCheckFailed
|
|
1473
|
+
const checkAgain = (
|
|
1474
|
+
<button
|
|
1475
|
+
type="button"
|
|
1476
|
+
disabled={checkingAgents}
|
|
1477
|
+
onClick={() => void recheckAgents().then(() => setCheckedState(setupState))}
|
|
1478
|
+
className="mt-3 flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium btn-brand rounded-md disabled:opacity-50"
|
|
1479
|
+
>
|
|
1480
|
+
<RotateCw className={clsx('w-3.5 h-3.5', checkingAgents && 'animate-spin')} />
|
|
1481
|
+
{checkingAgents ? 'Checking…' : 'Check again'}
|
|
1482
|
+
</button>
|
|
1483
|
+
)
|
|
1484
|
+
const panel = (title: string, body?: ReactNode, action?: ReactNode) => (
|
|
1426
1485
|
<div className="rounded-md border border-theme-border bg-theme-elevated/50 p-3">
|
|
1427
|
-
<p className="text-sm font-medium text-theme-text-primary">
|
|
1428
|
-
<p className="mt-1 text-xs text-theme-text-tertiary">
|
|
1429
|
-
|
|
1430
|
-
<span className="text-theme-text-secondary">Codex</span>,{' '}
|
|
1431
|
-
<span className="text-theme-text-secondary">Cursor</span> (
|
|
1432
|
-
<span className="font-mono">cursor-agent</span>), or{' '}
|
|
1433
|
-
<span className="text-theme-text-secondary">OpenCode</span>, then restart Radar — this tab
|
|
1434
|
-
will show the agent, model, and effort controls.
|
|
1435
|
-
</p>
|
|
1486
|
+
<p className="text-sm font-medium text-theme-text-primary">{title}</p>
|
|
1487
|
+
{body && <p className="mt-1 text-xs text-theme-text-tertiary">{body}</p>}
|
|
1488
|
+
{action}
|
|
1436
1489
|
</div>
|
|
1437
1490
|
)
|
|
1491
|
+
if (setupState === 'unknown') {
|
|
1492
|
+
// The agent probe hasn't answered. Saying anything about the CLI or the
|
|
1493
|
+
// deployment here would be a guess.
|
|
1494
|
+
return agentsCheckFailed && !checkingAgents
|
|
1495
|
+
? panel("Couldn't check for agent CLIs", 'Try again.', checkAgain)
|
|
1496
|
+
: panel('Checking…')
|
|
1497
|
+
}
|
|
1498
|
+
if (setupState === 'off') {
|
|
1499
|
+
return panel(
|
|
1500
|
+
'Not available in this deployment',
|
|
1501
|
+
'They need Radar running on your own computer, with MCP on and sign-in off.',
|
|
1502
|
+
)
|
|
1503
|
+
}
|
|
1504
|
+
if (cliOverride) {
|
|
1505
|
+
return panel(
|
|
1506
|
+
"Radar can't run your RADAR_AI_CLI_BIN",
|
|
1507
|
+
<>
|
|
1508
|
+
Fix the path, or remove it and restart Radar.
|
|
1509
|
+
{unchanged && " Still can't run it."}
|
|
1510
|
+
</>,
|
|
1511
|
+
checkAgain,
|
|
1512
|
+
)
|
|
1513
|
+
}
|
|
1514
|
+
if (setupState === 'needs-restart') {
|
|
1515
|
+
return panel(
|
|
1516
|
+
"AI investigations aren't available right now",
|
|
1517
|
+
<>
|
|
1518
|
+
{agentsCheckFailed && !checkingAgents ? "Couldn't check. Try again." : 'Try again in a moment.'}
|
|
1519
|
+
{unchanged && ' Still not available.'}
|
|
1520
|
+
</>,
|
|
1521
|
+
checkAgain,
|
|
1522
|
+
)
|
|
1523
|
+
}
|
|
1524
|
+
return panel(
|
|
1525
|
+
'No supported agent CLI found',
|
|
1526
|
+
<>
|
|
1527
|
+
Install <span className="text-theme-text-secondary">Claude Code</span>,{' '}
|
|
1528
|
+
<span className="text-theme-text-secondary">Codex</span>,{' '}
|
|
1529
|
+
<span className="text-theme-text-secondary">Cursor</span> (
|
|
1530
|
+
<span className="font-mono">cursor-agent</span>), or{' '}
|
|
1531
|
+
<span className="text-theme-text-secondary">OpenCode</span>, and this tab will show the
|
|
1532
|
+
agent, model, and effort controls.
|
|
1533
|
+
{agentsCheckFailed && !checkingAgents && " Couldn't check. Try again."}
|
|
1534
|
+
{unchanged && ' Still not found.'}
|
|
1535
|
+
</>,
|
|
1536
|
+
checkAgain,
|
|
1537
|
+
)
|
|
1438
1538
|
}
|
|
1439
1539
|
|
|
1440
1540
|
// -- Startup section bodies ---------------------------------------------------
|
|
@@ -2125,6 +2225,7 @@ function ArgoCDConfigField({
|
|
|
2125
2225
|
envError,
|
|
2126
2226
|
cliSession,
|
|
2127
2227
|
statusReason,
|
|
2228
|
+
connecting,
|
|
2128
2229
|
anonymous,
|
|
2129
2230
|
connectedAddress,
|
|
2130
2231
|
active,
|
|
@@ -2139,6 +2240,7 @@ function ArgoCDConfigField({
|
|
|
2139
2240
|
envError?: string
|
|
2140
2241
|
cliSession?: { server: string; user: string; insecure?: boolean }
|
|
2141
2242
|
statusReason?: string
|
|
2243
|
+
connecting?: boolean
|
|
2142
2244
|
anonymous?: boolean
|
|
2143
2245
|
connectedAddress?: string
|
|
2144
2246
|
active?: boolean
|
|
@@ -2147,7 +2249,7 @@ function ArgoCDConfigField({
|
|
|
2147
2249
|
onApplied?: (v: { url: string; insecureTls: boolean; tokenSet: boolean }) => void
|
|
2148
2250
|
}) {
|
|
2149
2251
|
if (envManaged) {
|
|
2150
|
-
return <ArgoCDEnvManagedField url={url} insecureTls={insecureTls} envError={envError} connectedAddress={connectedAddress} statusReason={statusReason} />
|
|
2252
|
+
return <ArgoCDEnvManagedField url={url} insecureTls={insecureTls} envError={envError} connectedAddress={connectedAddress} statusReason={statusReason} connecting={connecting} />
|
|
2151
2253
|
}
|
|
2152
2254
|
return (
|
|
2153
2255
|
<ArgoCDEditableField
|
|
@@ -2156,6 +2258,7 @@ function ArgoCDConfigField({
|
|
|
2156
2258
|
tokenSet={tokenSet}
|
|
2157
2259
|
cliSession={cliSession}
|
|
2158
2260
|
statusReason={statusReason}
|
|
2261
|
+
connecting={connecting}
|
|
2159
2262
|
anonymous={anonymous}
|
|
2160
2263
|
connectedAddress={connectedAddress}
|
|
2161
2264
|
active={active}
|
|
@@ -2176,6 +2279,7 @@ function ArgoCDEnvManagedField({
|
|
|
2176
2279
|
envError,
|
|
2177
2280
|
connectedAddress,
|
|
2178
2281
|
statusReason,
|
|
2282
|
+
connecting,
|
|
2179
2283
|
}: {
|
|
2180
2284
|
url: string
|
|
2181
2285
|
insecureTls: boolean
|
|
@@ -2185,6 +2289,7 @@ function ArgoCDEnvManagedField({
|
|
|
2185
2289
|
// so this is the one thing on it that can change after deploy.
|
|
2186
2290
|
connectedAddress?: string
|
|
2187
2291
|
statusReason?: string
|
|
2292
|
+
connecting?: boolean
|
|
2188
2293
|
}) {
|
|
2189
2294
|
if (envError) {
|
|
2190
2295
|
return (
|
|
@@ -2241,10 +2346,15 @@ function ArgoCDEnvManagedField({
|
|
|
2241
2346
|
<dt className="w-24 shrink-0 text-theme-text-tertiary">Status</dt>
|
|
2242
2347
|
<dd className="min-w-0 truncate text-theme-text-secondary">
|
|
2243
2348
|
{connectedAddress ? (
|
|
2244
|
-
|
|
2245
|
-
<
|
|
2246
|
-
|
|
2247
|
-
|
|
2349
|
+
<>
|
|
2350
|
+
<span className="inline-flex items-center gap-1">
|
|
2351
|
+
<Check className="w-3 h-3 shrink-0 text-green-600 dark:text-green-400/80" />
|
|
2352
|
+
Connected at {connectedAddress}
|
|
2353
|
+
</span>
|
|
2354
|
+
{statusReason && <span className="block whitespace-normal text-warning-text">{statusReason}</span>}
|
|
2355
|
+
</>
|
|
2356
|
+
) : connecting ? (
|
|
2357
|
+
'Connecting…'
|
|
2248
2358
|
) : statusReason ? (
|
|
2249
2359
|
<span className="text-warning-text">Not connected — {statusReason}</span>
|
|
2250
2360
|
) : (
|
|
@@ -2274,6 +2384,7 @@ function ArgoCDEditableField({
|
|
|
2274
2384
|
tokenSet,
|
|
2275
2385
|
cliSession,
|
|
2276
2386
|
statusReason,
|
|
2387
|
+
connecting: serverConnecting,
|
|
2277
2388
|
anonymous,
|
|
2278
2389
|
connectedAddress,
|
|
2279
2390
|
active,
|
|
@@ -2286,6 +2397,7 @@ function ArgoCDEditableField({
|
|
|
2286
2397
|
tokenSet: boolean
|
|
2287
2398
|
cliSession?: { server: string; user: string; insecure?: boolean }
|
|
2288
2399
|
statusReason?: string
|
|
2400
|
+
connecting?: boolean
|
|
2289
2401
|
anonymous?: boolean
|
|
2290
2402
|
connectedAddress?: string
|
|
2291
2403
|
active?: boolean
|
|
@@ -2414,6 +2526,10 @@ function ArgoCDEditableField({
|
|
|
2414
2526
|
</p>
|
|
2415
2527
|
)}
|
|
2416
2528
|
|
|
2529
|
+
{serverConnecting && state.status === 'idle' && !anonymous && !connectedAddress && (
|
|
2530
|
+
<p className="mb-3 text-xs text-theme-text-secondary">Connecting to Argo CD…</p>
|
|
2531
|
+
)}
|
|
2532
|
+
|
|
2417
2533
|
{statusReason && state.status !== 'connected' && (
|
|
2418
2534
|
<div className="mb-3 rounded-md border border-theme-border bg-theme-elevated p-3">
|
|
2419
2535
|
<p className="flex items-center gap-1.5 text-sm font-medium text-warning-text">
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { useState, useRef, useEffect, useMemo } from 'react'
|
|
1
|
+
import { useState, useRef, useEffect, useMemo, type ReactNode } from 'react'
|
|
2
2
|
import { Search } from 'lucide-react'
|
|
3
3
|
import { Input } from '@skyhook-io/k8s-ui'
|
|
4
4
|
|
|
5
|
-
export function LargeClusterNamespacePicker({ namespaces, onSelect }: {
|
|
5
|
+
export function LargeClusterNamespacePicker({ namespaces, onSelect, ranked = false, renderMeta }: {
|
|
6
6
|
namespaces: { name: string }[] | undefined
|
|
7
7
|
onSelect: (ns: string) => void
|
|
8
|
+
/** Keep the given order (already ranked) instead of sorting by name. */
|
|
9
|
+
ranked?: boolean
|
|
10
|
+
/** Extra detail shown at the end of a namespace's row. */
|
|
11
|
+
renderMeta?: (name: string) => ReactNode
|
|
8
12
|
}) {
|
|
9
13
|
const [search, setSearch] = useState('')
|
|
10
14
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
@@ -15,8 +19,9 @@ export function LargeClusterNamespacePicker({ namespaces, onSelect }: {
|
|
|
15
19
|
|
|
16
20
|
const sorted = useMemo(() => {
|
|
17
21
|
if (!namespaces) return []
|
|
22
|
+
if (ranked) return namespaces
|
|
18
23
|
return [...namespaces].sort((a, b) => a.name.localeCompare(b.name))
|
|
19
|
-
}, [namespaces])
|
|
24
|
+
}, [namespaces, ranked])
|
|
20
25
|
|
|
21
26
|
const filtered = useMemo(() => {
|
|
22
27
|
if (!search.trim()) return sorted
|
|
@@ -51,9 +56,10 @@ export function LargeClusterNamespacePicker({ namespaces, onSelect }: {
|
|
|
51
56
|
key={ns.name}
|
|
52
57
|
type="button"
|
|
53
58
|
onClick={() => onSelect(ns.name)}
|
|
54
|
-
className="w-full text-left px-3 py-2 text-sm text-theme-text-primary hover:bg-theme-hover transition-colors border-b border-theme-border last:border-b-0"
|
|
59
|
+
className="w-full flex items-center justify-between gap-3 text-left px-3 py-2 text-sm text-theme-text-primary hover:bg-theme-hover transition-colors border-b border-theme-border last:border-b-0"
|
|
55
60
|
>
|
|
56
|
-
{ns.name}
|
|
61
|
+
<span className="truncate">{ns.name}</span>
|
|
62
|
+
{renderMeta?.(ns.name)}
|
|
57
63
|
</button>
|
|
58
64
|
))
|
|
59
65
|
)}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo } from 'react'
|
|
2
2
|
import {
|
|
3
|
-
SEVERITY_TEXT,
|
|
4
3
|
TimelineList as TimelineListUI,
|
|
5
4
|
eventsForApplication,
|
|
6
5
|
isTimelineProblem,
|
|
@@ -13,9 +12,12 @@ import {
|
|
|
13
12
|
import type { TimelineEvent } from '../../types'
|
|
14
13
|
import { useTimelineSource } from '../../context/TimelineSource'
|
|
15
14
|
import { isManagedTimelineEvent } from '../../api/timelineSource'
|
|
15
|
+
import { TimelineRetentionBanner } from './TimelineRetentionNote'
|
|
16
16
|
import { useHasLimitedAccess } from '../../contexts/CapabilitiesContext'
|
|
17
17
|
import type { NavigateToResource } from '../../utils/navigation'
|
|
18
|
-
import {
|
|
18
|
+
import { TimelineLoadError, TimelineStaleNote } from './TimelineLoadError'
|
|
19
|
+
import { useStickyError } from './useStickyError'
|
|
20
|
+
import { ViewModeToggle } from './ViewModeToggle'
|
|
19
21
|
|
|
20
22
|
export type { ActivityTypeFilter, ActivityFilterKey }
|
|
21
23
|
|
|
@@ -92,9 +94,10 @@ interface TimelineListProps {
|
|
|
92
94
|
appScoped?: boolean
|
|
93
95
|
topology?: Topology
|
|
94
96
|
appScopeLoading?: boolean
|
|
97
|
+
onOpenDiagnostics?: () => void
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
export function TimelineList({ namespaces, onViewChange, currentView, onResourceClick, initialFilter, initialTimeRange, showDeleted, onShowDeletedChange, showRoutine, onShowRoutineChange, search, onSearchChange, activityFilter, onActivityFilterChange, kindFilter, onKindFilterChange, selectionWindow, onVisibleWindowChange, scrollToMs, focusedAppIndex, appScoped = false, topology, appScopeLoading = false }: TimelineListProps) {
|
|
100
|
+
export function TimelineList({ namespaces, onViewChange, currentView, onResourceClick, initialFilter, initialTimeRange, showDeleted, onShowDeletedChange, showRoutine, onShowRoutineChange, search, onSearchChange, activityFilter, onActivityFilterChange, kindFilter, onKindFilterChange, selectionWindow, onVisibleWindowChange, scrollToMs, focusedAppIndex, appScoped = false, topology, appScopeLoading = false, onOpenDiagnostics }: TimelineListProps) {
|
|
98
101
|
const hasLimitedAccess = useHasLimitedAccess()
|
|
99
102
|
const timelineSource = useTimelineSource()
|
|
100
103
|
const [queryParams, setQueryParams] = useState<{ timeRange: TimeRange; kinds: string[] }>({
|
|
@@ -111,7 +114,7 @@ export function TimelineList({ namespaces, onViewChange, currentView, onResource
|
|
|
111
114
|
const splitRows = !appScoped
|
|
112
115
|
// Uncapped when splitting: each part gets its own cap below. The rows come
|
|
113
116
|
// from the loaded ring, so this costs no extra fetch.
|
|
114
|
-
const { data: fetchedEvents, isLoading, isError, error, refetch } = timelineSource.useEvents({
|
|
117
|
+
const { data: fetchedEvents, isLoading, isFetching, isError, error, refetch } = timelineSource.useEvents({
|
|
115
118
|
namespaces,
|
|
116
119
|
kinds: queryParams.kinds,
|
|
117
120
|
timeRange: queryParams.timeRange,
|
|
@@ -122,6 +125,7 @@ export function TimelineList({ namespaces, onViewChange, currentView, onResource
|
|
|
122
125
|
fromMs: selectionWindow?.fromMs,
|
|
123
126
|
toMs: selectionWindow?.toMs,
|
|
124
127
|
})
|
|
128
|
+
const loadError = useStickyError(error, fetchedEvents != null)
|
|
125
129
|
const split = useMemo(
|
|
126
130
|
() => (splitRows && fetchedEvents ? splitRoutineActivity(fetchedEvents, fetchLimit) : undefined),
|
|
127
131
|
[splitRows, fetchedEvents, fetchLimit],
|
|
@@ -140,21 +144,22 @@ export function TimelineList({ namespaces, onViewChange, currentView, onResource
|
|
|
140
144
|
|
|
141
145
|
// Full-screen error only when nothing is loaded; a failing background poll
|
|
142
146
|
// with data on screen keeps rendering (data before error).
|
|
143
|
-
if (
|
|
147
|
+
if (loadError && !fetchedEvents) {
|
|
144
148
|
return (
|
|
145
|
-
<div className="flex flex-col
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
<div className="flex flex-col h-full">
|
|
150
|
+
{currentView && onViewChange && (
|
|
151
|
+
<div className="flex items-center justify-between px-4 py-2 border-b border-theme-border">
|
|
152
|
+
<div />
|
|
153
|
+
<ViewModeToggle viewMode={currentView} onViewModeChange={onViewChange} />
|
|
154
|
+
</div>
|
|
150
155
|
)}
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
<TimelineLoadError
|
|
157
|
+
className="flex-1"
|
|
158
|
+
error={loadError}
|
|
159
|
+
retrying={isFetching}
|
|
160
|
+
onRetry={() => refetch()}
|
|
161
|
+
onOpenDiagnostics={onOpenDiagnostics}
|
|
162
|
+
/>
|
|
158
163
|
</div>
|
|
159
164
|
)
|
|
160
165
|
}
|
|
@@ -164,17 +169,13 @@ export function TimelineList({ namespaces, onViewChange, currentView, onResource
|
|
|
164
169
|
// view-level banner already reports the shared failure, and a second note
|
|
165
170
|
// would double up.
|
|
166
171
|
const staleNote = isError && fetchedEvents && !selectionWindow ? (
|
|
167
|
-
<
|
|
168
|
-
<AlertTriangle className={`h-3.5 w-3.5 shrink-0 ${SEVERITY_TEXT.warning}`} />
|
|
169
|
-
Live updates are failing — the list may be stale.
|
|
170
|
-
<button type="button" onClick={() => refetch()} className="underline hover:text-theme-text-primary">
|
|
171
|
-
Retry now
|
|
172
|
-
</button>
|
|
173
|
-
</div>
|
|
172
|
+
<TimelineStaleNote error={error} subject="list" onRetry={() => refetch()} />
|
|
174
173
|
) : null
|
|
175
174
|
|
|
176
175
|
return (
|
|
177
176
|
<div className="flex h-full min-h-0 flex-1 flex-col">
|
|
177
|
+
{/* Under a scrubber TimelineView already shows the note above it. */}
|
|
178
|
+
{!selectionWindow && <TimelineRetentionBanner />}
|
|
178
179
|
{staleNote}
|
|
179
180
|
<div className="min-h-0 flex-1">
|
|
180
181
|
<TimelineListUI
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { ApiError } from '../../api/client'
|
|
4
|
+
import { TimelineLoadError, TimelineStaleNote } from './TimelineLoadError'
|
|
5
|
+
|
|
6
|
+
const driverError = 'open PostgreSQL timeline database: dial tcp 10.0.0.5:5432: connect: connection refused'
|
|
7
|
+
|
|
8
|
+
function databaseDown(): ApiError {
|
|
9
|
+
return new ApiError('Timeline history is unavailable because Radar can\'t connect to its PostgreSQL database.', 503, {
|
|
10
|
+
error_code: 'timeline_database_unreachable',
|
|
11
|
+
detail: driverError,
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function render(error: Error, onOpenDiagnostics?: () => void): string {
|
|
16
|
+
return renderToStaticMarkup(
|
|
17
|
+
<TimelineLoadError error={error} onRetry={() => {}} onOpenDiagnostics={onOpenDiagnostics} />,
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('TimelineLoadError', () => {
|
|
22
|
+
it('names the database and points to the logs when PostgreSQL is unreachable', () => {
|
|
23
|
+
const html = render(databaseDown(), () => {})
|
|
24
|
+
expect(html).toContain('Timeline history is unavailable')
|
|
25
|
+
expect(html).toContain('can't connect to its PostgreSQL database')
|
|
26
|
+
expect(html).toContain('[timeline]')
|
|
27
|
+
// The driver's error names the database host; it belongs in the logs and
|
|
28
|
+
// diagnostics, not on the page.
|
|
29
|
+
expect(html).not.toContain(driverError)
|
|
30
|
+
expect(html).toContain('Open diagnostics')
|
|
31
|
+
expect(html).not.toContain('Failed to load timeline data')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('omits the diagnostics link when the host cannot open diagnostics', () => {
|
|
35
|
+
expect(render(databaseDown())).not.toContain('Open diagnostics')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('keeps the generic state, without a diagnostics link, for other failures', () => {
|
|
39
|
+
const html = render(new ApiError('upstream timed out', 502, { error: 'upstream timed out' }), () => {})
|
|
40
|
+
expect(html).toContain('Failed to load timeline data')
|
|
41
|
+
expect(html).toContain('upstream timed out')
|
|
42
|
+
expect(html).not.toContain('Open diagnostics')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('TimelineStaleNote', () => {
|
|
47
|
+
const note = (error: Error) =>
|
|
48
|
+
renderToStaticMarkup(<TimelineStaleNote error={error} subject="timeline" onRetry={() => {}} />)
|
|
49
|
+
|
|
50
|
+
it('names the database and points to the logs when PostgreSQL is the cause', () => {
|
|
51
|
+
const html = note(databaseDown())
|
|
52
|
+
expect(html).toContain('can't connect to its PostgreSQL database, so the timeline may be stale')
|
|
53
|
+
expect(html).toContain('[timeline]')
|
|
54
|
+
expect(html).not.toContain(driverError)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('keeps the generic note for other failures', () => {
|
|
58
|
+
const html = note(new ApiError('upstream timed out', 502, { error: 'upstream timed out' }))
|
|
59
|
+
expect(html).toContain('Live updates are failing, so the timeline may be stale')
|
|
60
|
+
expect(html).not.toContain('PostgreSQL')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { clsx } from 'clsx'
|
|
2
|
+
import { AlertTriangle, Bug, RefreshCw } from 'lucide-react'
|
|
3
|
+
import { timelineDatabaseError } from '../../api/client'
|
|
4
|
+
import { SEVERITY_TEXT } from '@skyhook-io/k8s-ui'
|
|
5
|
+
|
|
6
|
+
const SECONDARY_BUTTON =
|
|
7
|
+
'flex items-center gap-2 px-3 py-1.5 text-sm bg-theme-elevated border border-theme-border-light rounded-lg hover:bg-theme-hover transition-colors'
|
|
8
|
+
|
|
9
|
+
// TimelineLoadError is the full-screen state when the timeline has nothing to
|
|
10
|
+
// show. An unreachable PostgreSQL database gets its own copy: it says the
|
|
11
|
+
// database is the problem and where to look. The driver's error stays in
|
|
12
|
+
// Radar's logs and the diagnostics overlay.
|
|
13
|
+
export function TimelineLoadError({
|
|
14
|
+
error,
|
|
15
|
+
onRetry,
|
|
16
|
+
retrying = false,
|
|
17
|
+
onOpenDiagnostics,
|
|
18
|
+
className,
|
|
19
|
+
}: {
|
|
20
|
+
error?: Error | null
|
|
21
|
+
onRetry: () => void
|
|
22
|
+
/** A request is in flight: the error stays up, the button shows the attempt. */
|
|
23
|
+
retrying?: boolean
|
|
24
|
+
onOpenDiagnostics?: () => void
|
|
25
|
+
className?: string
|
|
26
|
+
}) {
|
|
27
|
+
const databaseError = timelineDatabaseError(error)
|
|
28
|
+
const detail = error?.message?.trim()
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className={clsx('flex flex-col items-center justify-center text-theme-text-tertiary gap-3 px-6', className)}>
|
|
32
|
+
<AlertTriangle className="w-10 h-10 text-amber-400/70" />
|
|
33
|
+
{databaseError !== null ? (
|
|
34
|
+
<>
|
|
35
|
+
<p className="text-base text-theme-text-secondary">Timeline history is unavailable</p>
|
|
36
|
+
<p className="max-w-md text-center text-sm">
|
|
37
|
+
Radar can't connect to its PostgreSQL database, so changes aren't being recorded. It keeps
|
|
38
|
+
retrying, and this page recovers on its own once the database is back.
|
|
39
|
+
</p>
|
|
40
|
+
<p className="max-w-md text-center text-xs">
|
|
41
|
+
Radar's logs have the details: search them for <code className="font-mono">[timeline]</code>.
|
|
42
|
+
</p>
|
|
43
|
+
</>
|
|
44
|
+
) : (
|
|
45
|
+
<>
|
|
46
|
+
<p className="text-base">Failed to load timeline data</p>
|
|
47
|
+
{detail && <p className="max-w-md text-center text-sm">{detail}</p>}
|
|
48
|
+
</>
|
|
49
|
+
)}
|
|
50
|
+
<div className="flex items-center gap-2">
|
|
51
|
+
<button type="button" onClick={onRetry} disabled={retrying} className={clsx(SECONDARY_BUTTON, 'disabled:opacity-60 disabled:cursor-default')}>
|
|
52
|
+
<RefreshCw className={clsx('w-3.5 h-3.5', retrying && 'animate-spin')} />
|
|
53
|
+
Try again
|
|
54
|
+
</button>
|
|
55
|
+
{databaseError !== null && onOpenDiagnostics && (
|
|
56
|
+
<button type="button" onClick={onOpenDiagnostics} className={SECONDARY_BUTTON}>
|
|
57
|
+
<Bug className="w-3.5 h-3.5" />
|
|
58
|
+
Open diagnostics
|
|
59
|
+
</button>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// TimelineStaleNote is the banner over data that is still on screen while its
|
|
67
|
+
// refresh fails. When the database is the cause it says so and where to look,
|
|
68
|
+
// since the data underneath otherwise reads as current.
|
|
69
|
+
export function TimelineStaleNote({
|
|
70
|
+
error,
|
|
71
|
+
subject,
|
|
72
|
+
onRetry,
|
|
73
|
+
}: {
|
|
74
|
+
error?: Error | null
|
|
75
|
+
/** What may be stale: the swimlane view or the list. */
|
|
76
|
+
subject: 'timeline' | 'list'
|
|
77
|
+
onRetry: () => void
|
|
78
|
+
}) {
|
|
79
|
+
const databaseDown = timelineDatabaseError(error) !== null
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex items-center gap-1.5 border-b border-theme-border px-4 py-1.5 text-xs text-theme-text-tertiary">
|
|
82
|
+
<AlertTriangle className={`h-3.5 w-3.5 shrink-0 ${SEVERITY_TEXT.warning}`} />
|
|
83
|
+
{databaseDown ? (
|
|
84
|
+
<span>
|
|
85
|
+
Radar can't connect to its PostgreSQL database, so the {subject} may be stale. Search Radar's logs
|
|
86
|
+
for <code className="font-mono">[timeline]</code>.
|
|
87
|
+
</span>
|
|
88
|
+
) : (
|
|
89
|
+
<span>Live updates are failing, so the {subject} may be stale.</span>
|
|
90
|
+
)}
|
|
91
|
+
<button type="button" onClick={onRetry} className="underline hover:text-theme-text-primary">
|
|
92
|
+
Retry now
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|