@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
|
@@ -35,7 +35,9 @@ describe('Metrics help', () => {
|
|
|
35
35
|
expect(html).toContain('--prometheus-single-cluster')
|
|
36
36
|
expect(html).toContain('they do not add missing metrics')
|
|
37
37
|
expect(html).toContain('Leave both unset for automatic matching')
|
|
38
|
-
expect(html).toContain('
|
|
38
|
+
expect(html).toContain('It does not scope rightsizing')
|
|
39
|
+
expect(html).toContain('Pod comparison and PVC usage')
|
|
40
|
+
expect(html).toContain('labels must exist on the kubelet volume stats')
|
|
39
41
|
expect(html).toContain('Standalone CLI:')
|
|
40
42
|
expect(html).toContain('when starting Radar')
|
|
41
43
|
expect(html).toContain('traffic.prometheusSingleCluster')
|
|
@@ -133,7 +133,7 @@ export function WorkloadMetricsHelpContent({ data, pending, error }: Pick<Props,
|
|
|
133
133
|
<div><dt className="font-medium">Backend dedicated to this cluster</dt><dd><code className="break-all font-mono text-xs">--prometheus-single-cluster</code><p className="mt-1">Helm: <code className="break-all font-mono text-xs">traffic.prometheusSingleCluster: true</code></p></dd></div>
|
|
134
134
|
<div><dt className="font-medium">Shared backend with a cluster label</dt><dd><code className="break-all font-mono text-xs">--prometheus-cluster-label cluster=your-cluster</code><p className="mt-1">Helm: set the backend’s actual label and value in <code className="break-all font-mono text-xs">traffic.prometheusClusterLabels</code>.</p></dd></div>
|
|
135
135
|
</dl>
|
|
136
|
-
<p>Leave both unset for automatic matching. An override replaces automatic matching for workload requests, resources, history
|
|
136
|
+
<p>Leave both unset for automatic matching. An override replaces automatic matching for workload requests, resources, history, Pod comparison and PVC usage. For PVC usage, its labels must exist on the kubelet volume stats. It does not scope rightsizing, node/HPA or legacy network/storage metrics.</p>
|
|
137
137
|
<h4 className="font-medium text-theme-text-primary">Operator configuration</h4>
|
|
138
138
|
<p><strong className="font-medium">Standalone CLI:</strong> supply the flag when starting Radar. Overrides are not saved; after changing the cluster, backend or credentials, verify the new scope before restarting with a fresh override.</p>
|
|
139
139
|
<p><strong className="font-medium">In-cluster OSS or Radar Cloud:</strong> ask the installation’s operator to update its Helm values or GitOps configuration. These are shared backend settings, not viewer preferences.</p>
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type CrossplaneResourceRef,
|
|
13
13
|
} from '@skyhook-io/k8s-ui/components/resources/resource-utils-crossplane'
|
|
14
14
|
import { getResourceStatus } from '@skyhook-io/k8s-ui'
|
|
15
|
-
import {
|
|
15
|
+
import { kindToPluralWithGroup } from '@skyhook-io/k8s-ui/utils/navigation'
|
|
16
16
|
import type { ResourceRef as NavRef } from '../../types'
|
|
17
17
|
import { fetchJSON, ApiError } from '../../api/client'
|
|
18
18
|
|
|
@@ -59,8 +59,8 @@ export function CompositeRenderer({ data, onNavigate }: CompositeRendererProps)
|
|
|
59
59
|
],
|
|
60
60
|
queryFn: async () => {
|
|
61
61
|
const ns = boundXRRef!.namespace || '_'
|
|
62
|
-
const plural = kindToPlural(boundXRRef!.kind)
|
|
63
62
|
const group = groupFromApiVersion(boundXRRef!.apiVersion)
|
|
63
|
+
const plural = kindToPluralWithGroup(boundXRRef!.kind, group)
|
|
64
64
|
const query = group ? `?group=${encodeURIComponent(group)}` : ''
|
|
65
65
|
return fetchJSON<{ resource: any }>(`/resources/${plural}/${ns}/${boundXRRef!.name}${query}`)
|
|
66
66
|
},
|
|
@@ -99,7 +99,7 @@ export function CompositeRenderer({ data, onNavigate }: CompositeRendererProps)
|
|
|
99
99
|
queryKey: ['composed-ref', group, ref.kind, ref.namespace ?? '', ref.name],
|
|
100
100
|
queryFn: async () => {
|
|
101
101
|
const ns = ref.namespace || '_'
|
|
102
|
-
const plural =
|
|
102
|
+
const plural = kindToPluralWithGroup(ref.kind, group)
|
|
103
103
|
const query = group ? `?group=${encodeURIComponent(group)}` : ''
|
|
104
104
|
return fetchJSON<{ resource: any }>(`/resources/${plural}/${ns}/${ref.name}${query}`)
|
|
105
105
|
},
|
|
@@ -137,7 +137,7 @@ export function CompositeRenderer({ data, onNavigate }: CompositeRendererProps)
|
|
|
137
137
|
map.set(key, { ref, missing: true })
|
|
138
138
|
return
|
|
139
139
|
}
|
|
140
|
-
const status = getResourceStatus(
|
|
140
|
+
const status = getResourceStatus(kindToPluralWithGroup(ref.kind, groupFromApiVersion(ref.apiVersion)), q.data.resource) ?? undefined
|
|
141
141
|
map.set(key, {
|
|
142
142
|
ref,
|
|
143
143
|
status: status ? { ...status, level: (status as any).level ?? 'unknown' } : undefined,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ResourceDetailDrawer as BaseResourceDetailDrawer } from '@skyhook-io/k8s-ui'
|
|
2
2
|
import type { SelectedResource } from '../../types'
|
|
3
3
|
import { WorkloadView } from '../workload/WorkloadView'
|
|
4
|
-
import {
|
|
4
|
+
import { ResourceDrawerNavigation } from '../../integrations/resourceHosts'
|
|
5
5
|
|
|
6
6
|
interface ResourceDetailDrawerProps {
|
|
7
7
|
resource: SelectedResource
|
|
@@ -33,7 +33,7 @@ export function ResourceDetailDrawer(props: ResourceDetailDrawerProps) {
|
|
|
33
33
|
<BaseResourceDetailDrawer {...props}>
|
|
34
34
|
{({ resource, expanded, active, initialTab, onClose, onExpand, onExpandIntent, onCancelExpandIntent, onBack, onNavigateToResource, onCollapseToDrawer }) => (
|
|
35
35
|
<div className="flex h-full min-h-0 flex-col">
|
|
36
|
-
{!expanded && <
|
|
36
|
+
{!expanded && <ResourceDrawerNavigation resource={resource} />}
|
|
37
37
|
<div className="min-h-0 flex-1">
|
|
38
38
|
<WorkloadView
|
|
39
39
|
kind={resource.kind}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo, useCallback
|
|
1
|
+
import { useState, useMemo, useCallback } from 'react'
|
|
2
2
|
import { useLocation, useNavigate } from 'react-router-dom'
|
|
3
3
|
import { useQuery } from '@tanstack/react-query'
|
|
4
4
|
import { ApiError, debugNamespaceLog, fetchJSON, isForbiddenError, isKindSyncFailed, isStillLoadingError, useCapabilities, useNamespaceCapabilities, useResources, useSecretCertExpiry, useTopPodMetrics, useTopNodeMetrics, useBulkDeleteResources, useBulkRestartWorkloads, useBulkScaleWorkloads, useAudit } from '../../api/client'
|
|
@@ -8,10 +8,11 @@ import { apiUrl, getAuthHeaders, getCredentialsMode, stripBasename } from '../..
|
|
|
8
8
|
import { readErrorBody } from '../../api/httpErrors'
|
|
9
9
|
import { useAPIResources } from '../../api/apiResources'
|
|
10
10
|
import { useConnection } from '../../context/ConnectionContext'
|
|
11
|
-
import {
|
|
11
|
+
import { getSecretStoreProviderType } from '@skyhook-io/k8s-ui'
|
|
12
12
|
import { usePinnedKinds } from '../../hooks/useFavorites'
|
|
13
13
|
import { useResourceCounts } from '../../hooks/useResourceCounts'
|
|
14
14
|
import { useCNPGSidebarWorkspace } from '../cnpg/useCNPGSidebarWorkspace'
|
|
15
|
+
import { renderResourceKindList, resourceKindListMode, useResourceHostFeatures } from '../../integrations/resourceHosts'
|
|
15
16
|
import { useOpenLogs, useOpenWorkloadLogs } from '../dock'
|
|
16
17
|
import {
|
|
17
18
|
canBulkRestartKind,
|
|
@@ -35,6 +36,9 @@ interface ResourcesViewProps {
|
|
|
35
36
|
onResourceClickYaml?: NavigateToResource
|
|
36
37
|
onKindChange?: () => void
|
|
37
38
|
onClearNamespaces?: () => void
|
|
39
|
+
rememberedKindSearch?: (kind: { name: string; group: string }) => string | undefined
|
|
40
|
+
onKindFiltersChange?: (kind: { name: string; group: string }, search: string) => void
|
|
41
|
+
onApplyNamespacesToAllSections?: (namespaces: string[]) => Promise<unknown> | void
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
type SelectedKindInfo = { name: string; kind: string; group: string } | null
|
|
@@ -70,12 +74,13 @@ function hasResourceCount(counts: Record<string, number> | undefined, key: strin
|
|
|
70
74
|
return Object.prototype.hasOwnProperty.call(counts ?? {}, key)
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
export function ResourcesView({ namespaces, selectedResource, onResourceClick, onResourceClickYaml, onKindChange, onClearNamespaces }: ResourcesViewProps) {
|
|
77
|
+
export function ResourcesView({ namespaces, selectedResource, onResourceClick, onResourceClickYaml, onKindChange, onClearNamespaces, rememberedKindSearch, onKindFiltersChange, onApplyNamespacesToAllSections }: ResourcesViewProps) {
|
|
74
78
|
const location = useLocation()
|
|
75
79
|
const navigate = useNavigate()
|
|
76
80
|
const { connection } = useConnection()
|
|
77
81
|
|
|
78
|
-
const { data: capabilities } = useCapabilities()
|
|
82
|
+
const { data: capabilities, isPending: capabilitiesPending } = useCapabilities()
|
|
83
|
+
const hostFeatures = useResourceHostFeatures()
|
|
79
84
|
const namespaceForCapabilities = namespaces.length === 1 ? namespaces[0] : undefined
|
|
80
85
|
const { data: namespaceCapabilities } = useNamespaceCapabilities(namespaceForCapabilities, capabilities)
|
|
81
86
|
const namespaceCapabilityNames = useMemo(() => namespaces.length > 1 ? [...namespaces].sort() : [], [namespaces])
|
|
@@ -104,15 +109,9 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
104
109
|
// API resources discovery
|
|
105
110
|
const { data: apiResources } = useAPIResources()
|
|
106
111
|
|
|
107
|
-
// Initialize navigation kind↔plural maps from discovered API resources
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
if (apiResources) initNavigationMap(apiResources)
|
|
110
|
-
}, [apiResources])
|
|
111
|
-
|
|
112
|
-
const cnpgSidebarWorkspace = useCNPGSidebarWorkspace({ apiResources, namespaces })
|
|
113
|
-
|
|
114
112
|
// Track the selected kind from the k8s-ui component
|
|
115
113
|
const [selectedKind, setSelectedKind] = useState<SelectedKindInfo>(null)
|
|
114
|
+
const cnpgSidebarWorkspace = useCNPGSidebarWorkspace({ apiResources, namespaces, withMetrics: selectedKind?.group === 'postgresql.cnpg.io' })
|
|
116
115
|
const workloadWrites = namespaces.length === 0
|
|
117
116
|
? capabilities?.workloadWrites
|
|
118
117
|
: namespaces.length === 1
|
|
@@ -281,7 +280,7 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
281
280
|
printerTable: sanitizePrinterTable(body),
|
|
282
281
|
}
|
|
283
282
|
},
|
|
284
|
-
enabled: !!selectedKind && !selectedKindQueryBlocked,
|
|
283
|
+
enabled: !!selectedKind && !selectedKindQueryBlocked && resourceKindListMode(selectedKind, hostFeatures, capabilitiesPending) === 'table',
|
|
285
284
|
staleTime: 30000,
|
|
286
285
|
refetchInterval: 120000, // Safety net — SSE k8s_event drives near-real-time invalidation
|
|
287
286
|
retry: (failureCount: number, error: Error) => {
|
|
@@ -385,6 +384,17 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
385
384
|
setCreateDialogOpen(true)
|
|
386
385
|
}, [])
|
|
387
386
|
|
|
387
|
+
const renderKindView = useCallback(
|
|
388
|
+
(kind: SelectedKindInfo) => renderResourceKindList(kind, hostFeatures, capabilitiesPending, {
|
|
389
|
+
namespaces,
|
|
390
|
+
inspected: selectedResource ?? null,
|
|
391
|
+
onInspect: (resource) => onResourceClick?.(resource),
|
|
392
|
+
onClearNamespaces: onClearNamespaces ?? (() => {}),
|
|
393
|
+
onCreate: handleCreateResource,
|
|
394
|
+
}),
|
|
395
|
+
[hostFeatures, capabilitiesPending, namespaces, selectedResource, onResourceClick, onClearNamespaces, handleCreateResource],
|
|
396
|
+
)
|
|
397
|
+
|
|
388
398
|
return (
|
|
389
399
|
<>
|
|
390
400
|
<BaseResourcesView
|
|
@@ -395,6 +405,9 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
395
405
|
onResourceClickYaml={onResourceClickYaml}
|
|
396
406
|
onKindChange={onKindChange}
|
|
397
407
|
onClearNamespaces={onClearNamespaces}
|
|
408
|
+
rememberedKindSearch={rememberedKindSearch}
|
|
409
|
+
onKindFiltersChange={onKindFiltersChange}
|
|
410
|
+
onApplyNamespacesToAllSections={onApplyNamespacesToAllSections}
|
|
398
411
|
// Injected data
|
|
399
412
|
apiResources={apiResources}
|
|
400
413
|
// Lightweight counts for sidebar (replaces 233 parallel queries)
|
|
@@ -434,6 +447,7 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
434
447
|
onOpenWorkloadLogs={openWorkloadLogs}
|
|
435
448
|
// Create resource
|
|
436
449
|
onCreateResource={handleCreateResource}
|
|
450
|
+
renderKindView={renderKindView}
|
|
437
451
|
// Bulk operations
|
|
438
452
|
onBulkDelete={(items, options) => bulkDeleteMutation.mutate({ items, force: options?.force }, { onSuccess: options?.onSuccess })}
|
|
439
453
|
isBulkDeleting={bulkDeleteMutation.isPending}
|
|
@@ -7,6 +7,7 @@ import { formatAge } from '@skyhook-io/k8s-ui/components/resources/resource-util
|
|
|
7
7
|
import { isForbiddenError } from '../../../api/client'
|
|
8
8
|
import { isKyvernoMutateExistingEnabled } from '@skyhook-io/k8s-ui/components/resources/resource-utils-kyverno-modern'
|
|
9
9
|
import { usePolicyQueued } from '../../../api/policy'
|
|
10
|
+
import { TimeValue } from '@skyhook-io/k8s-ui/components/ui/ScheduleValue'
|
|
10
11
|
|
|
11
12
|
/** A request sitting this long is not mid-flight. Kyverno retries on every
|
|
12
13
|
* reconcile, so past this point a backlog grows rather than drains. */
|
|
@@ -160,7 +161,7 @@ export function KyvernoPolicyQueued({ data }: { data: any }) {
|
|
|
160
161
|
/>
|
|
161
162
|
))}
|
|
162
163
|
{oldestPending && (
|
|
163
|
-
<Property label="Oldest Pending" value={
|
|
164
|
+
<Property label="Oldest Pending" value={<TimeValue timestamp={oldestPending} />} />
|
|
164
165
|
)}
|
|
165
166
|
</PropertyList>
|
|
166
167
|
{messages.length > 0 && (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NodeRenderer as BaseNodeRenderer } from '@skyhook-io/k8s-ui/components/resources/renderers/NodeRenderer'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
|
-
import { getVisibleLiveMetrics, isLiveMetricsUnavailable, shouldFetchLiveMetrics, useNodeMetrics, useNodeMetricsHistory, usePrometheusResourceMetrics, usePrometheusStatus } from '../../../api/client'
|
|
3
|
+
import { getVisibleLiveMetrics, isLiveMetricsUnavailable, shouldFetchLiveMetrics, useNodeRemovalPlan, useNodeMetrics, useNodeMetricsHistory, usePrometheusResourceMetrics, usePrometheusStatus } from '../../../api/client'
|
|
4
|
+
import { getNodeLifecycle } from '@skyhook-io/k8s-ui/utils/node-lifecycle'
|
|
4
5
|
import { serializeColumnFilters } from '../resource-utils'
|
|
5
6
|
|
|
6
7
|
interface NodeRendererProps {
|
|
@@ -11,6 +12,8 @@ interface NodeRendererProps {
|
|
|
11
12
|
export function NodeRenderer({ data, relationships }: NodeRendererProps) {
|
|
12
13
|
const navigate = useNavigate()
|
|
13
14
|
const nodeName = data.metadata?.name
|
|
15
|
+
const lifecycle = getNodeLifecycle(data)
|
|
16
|
+
const removalPlan = useNodeRemovalPlan(nodeName, lifecycle.removing)
|
|
14
17
|
|
|
15
18
|
// Fetch node metrics
|
|
16
19
|
const metricsHistoryQuery = useNodeMetricsHistory(nodeName)
|
|
@@ -35,6 +38,9 @@ export function NodeRenderer({ data, relationships }: NodeRendererProps) {
|
|
|
35
38
|
return (
|
|
36
39
|
<BaseNodeRenderer
|
|
37
40
|
data={data}
|
|
41
|
+
removalPlan={removalPlan.data}
|
|
42
|
+
removalPlanLoading={removalPlan.isLoading}
|
|
43
|
+
removalPlanError={removalPlan.error?.message}
|
|
38
44
|
relationships={relationships}
|
|
39
45
|
onViewPods={nodeName ? () => {
|
|
40
46
|
const params = new URLSearchParams()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { beforeEach, expect, it, vi } from 'vitest'
|
|
3
|
+
import { RadarFeatureUnsupportedError } from '../../../api/radarFeatures'
|
|
4
|
+
import { ServiceRenderer } from './ServiceRenderer'
|
|
5
|
+
|
|
6
|
+
const mock = vi.hoisted(() => ({
|
|
7
|
+
serviceQuery: {} as any,
|
|
8
|
+
namespaceQuery: {} as any,
|
|
9
|
+
serviceReads: [] as any[],
|
|
10
|
+
namespaceReads: [] as any[],
|
|
11
|
+
}))
|
|
12
|
+
vi.mock('../../../api/client', () => ({
|
|
13
|
+
useServiceEndpointSlices: (...args: any[]) => { mock.serviceReads.push(args); return mock.serviceQuery },
|
|
14
|
+
useResources: (...args: any[]) => { mock.namespaceReads.push(args); return mock.namespaceQuery },
|
|
15
|
+
}))
|
|
16
|
+
vi.mock('../../../contexts/CapabilitiesContext', () => ({ useNamespacedCapabilities: () => ({ canPortForward: false }), useIsLocalDeployment: () => true }))
|
|
17
|
+
vi.mock('../../portforward/PortForwardButton', () => ({ PortForwardInlineButton: () => null }))
|
|
18
|
+
vi.mock('../../curl/ServiceCurlButton', () => ({ CurlButton: () => null, CurlPanel: () => null, isHttpishPort: () => false, defaultScheme: () => '', defaultPathForPort: () => '' }))
|
|
19
|
+
|
|
20
|
+
const service = { apiVersion: 'v1', kind: 'Service', metadata: { name: 'web', namespace: 'team' }, spec: { selector: { app: 'web' } } }
|
|
21
|
+
const selectorless = { ...service, spec: {} }
|
|
22
|
+
const slice = (name: string, labels?: Record<string, string>) => ({ apiVersion: 'discovery.k8s.io/v1', kind: 'EndpointSlice', metadata: { namespace: 'team', name, labels } })
|
|
23
|
+
const render = (data: any = service) => renderToStaticMarkup(<ServiceRenderer data={data} onCopy={() => {}} copied={null} />)
|
|
24
|
+
const unsupported = () => new RadarFeatureUnsupportedError('serviceEndpointSlices', { currentVersion: 'v1.7.0' })
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
mock.serviceQuery = {}
|
|
28
|
+
mock.namespaceQuery = {}
|
|
29
|
+
mock.serviceReads = []
|
|
30
|
+
mock.namespaceReads = []
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('renders the server-selected slices for a selector Service without listing the namespace', () => {
|
|
34
|
+
mock.serviceQuery = { data: { items: [slice('web-abc', { 'kubernetes.io/service-name': 'web' })], truncated: false } }
|
|
35
|
+
const html = render()
|
|
36
|
+
expect(mock.serviceReads[0]).toEqual(['team', 'web', true])
|
|
37
|
+
expect(mock.namespaceReads[0][3].enabled).toBe(false)
|
|
38
|
+
expect(html).toContain('EndpointSlices')
|
|
39
|
+
expect(html).toContain('web-abc')
|
|
40
|
+
expect(html).not.toContain('Showing the first')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('says when the server cut the inventory off', () => {
|
|
44
|
+
mock.serviceQuery = { data: { items: [slice('web-abc')], truncated: true } }
|
|
45
|
+
expect(render()).toContain('Showing the first 1 EndpointSlices.')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('surfaces a failed lookup instead of an empty inventory', () => {
|
|
49
|
+
mock.serviceQuery = { error: new Error('insufficient permissions to list endpointslices') }
|
|
50
|
+
const html = render()
|
|
51
|
+
expect(html).toContain('EndpointSlice inventory unavailable')
|
|
52
|
+
expect(html).toContain('insufficient permissions to list endpointslices')
|
|
53
|
+
expect(html).not.toContain('No EndpointSlices found')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('does not query slices for ExternalName Services', () => {
|
|
57
|
+
const html = render({ ...service, spec: { type: 'ExternalName', externalName: 'example.org' } })
|
|
58
|
+
expect(mock.serviceReads[0][2]).toBe(false)
|
|
59
|
+
expect(mock.namespaceReads[0][3].enabled).toBe(false)
|
|
60
|
+
expect(html).not.toContain('EndpointSlices')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('falls back to the label-filtered namespace list for selector-less Services on an older Radar', () => {
|
|
64
|
+
mock.serviceQuery = { error: unsupported() }
|
|
65
|
+
mock.namespaceQuery = { data: [
|
|
66
|
+
slice('manual-abc', { 'kubernetes.io/service-name': 'web' }),
|
|
67
|
+
slice('other-abc', { 'kubernetes.io/service-name': 'other' }),
|
|
68
|
+
] }
|
|
69
|
+
const html = render(selectorless)
|
|
70
|
+
expect(mock.namespaceReads[0]).toEqual(['endpointslices', 'team', 'discovery.k8s.io', { enabled: true, refetchInterval: 30000 }])
|
|
71
|
+
expect(html).toContain('manual-abc')
|
|
72
|
+
expect(html).not.toContain('other-abc')
|
|
73
|
+
expect(html).not.toContain('EndpointSlice inventory unavailable')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('keeps selector Services free of slice lookups on an older Radar', () => {
|
|
77
|
+
mock.serviceQuery = { error: unsupported() }
|
|
78
|
+
const html = render()
|
|
79
|
+
expect(mock.namespaceReads[0][3].enabled).toBe(false)
|
|
80
|
+
expect(html).not.toContain('EndpointSlices')
|
|
81
|
+
})
|
|
@@ -2,7 +2,8 @@ import { useMemo, useState, useCallback } from 'react'
|
|
|
2
2
|
import { ServiceRenderer as BaseServiceRenderer } from '@skyhook-io/k8s-ui/components/resources/renderers/ServiceRenderer'
|
|
3
3
|
import { PortForwardInlineButton } from '../../portforward/PortForwardButton'
|
|
4
4
|
import { CurlButton, CurlPanel, isHttpishPort, defaultScheme, defaultPathForPort } from '../../curl/ServiceCurlButton'
|
|
5
|
-
import { useResources } from '../../../api/client'
|
|
5
|
+
import { useResources, useServiceEndpointSlices } from '../../../api/client'
|
|
6
|
+
import { isRadarFeatureUnsupported } from '../../../api/radarFeatures'
|
|
6
7
|
import { useNamespacedCapabilities, useIsLocalDeployment } from '../../../contexts/CapabilitiesContext'
|
|
7
8
|
import type { ResourceRef } from '../../../types'
|
|
8
9
|
import { DURATION_DISCLOSURE } from '@skyhook-io/k8s-ui/utils/animation'
|
|
@@ -36,21 +37,22 @@ export function ServiceRenderer({ data, onCopy, copied, onNavigate }: ServiceRen
|
|
|
36
37
|
window.setTimeout(() => setCurl((p) => (p?.closing ? null : p)), DURATION_DISCLOSURE + 20)
|
|
37
38
|
}, [])
|
|
38
39
|
const spec = data.spec || {}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
const
|
|
40
|
+
const hasNoSelector = !spec.selector || Object.keys(spec.selector).length === 0
|
|
41
|
+
const canListEndpointSlices = Boolean(namespace && serviceName && spec.type !== 'ExternalName')
|
|
42
|
+
const serviceSlices = useServiceEndpointSlices(namespace, serviceName, canListEndpointSlices)
|
|
43
|
+
// A Radar without the per-Service endpoint keeps its earlier behavior: only
|
|
44
|
+
// selector-less Services list the namespace's slices and filter them here.
|
|
45
|
+
const legacy = isRadarFeatureUnsupported(serviceSlices.error, 'serviceEndpointSlices')
|
|
46
|
+
const shouldLoadNamespaceSlices = canListEndpointSlices && hasNoSelector && legacy
|
|
47
|
+
const { data: namespaceSlices, isLoading: namespaceSlicesLoading, error: namespaceSlicesError } = useResources<any>(
|
|
46
48
|
'endpointslices',
|
|
47
49
|
namespace,
|
|
48
50
|
'discovery.k8s.io',
|
|
49
|
-
{ enabled:
|
|
51
|
+
{ enabled: shouldLoadNamespaceSlices, refetchInterval: 30000 }
|
|
50
52
|
)
|
|
51
|
-
const
|
|
52
|
-
() => (
|
|
53
|
-
[
|
|
53
|
+
const matchingNamespaceSlices = useMemo(
|
|
54
|
+
() => (namespaceSlices || []).filter((slice: any) => slice.metadata?.labels?.['kubernetes.io/service-name'] === serviceName),
|
|
55
|
+
[namespaceSlices, serviceName]
|
|
54
56
|
)
|
|
55
57
|
|
|
56
58
|
return (
|
|
@@ -58,8 +60,11 @@ export function ServiceRenderer({ data, onCopy, copied, onNavigate }: ServiceRen
|
|
|
58
60
|
data={data}
|
|
59
61
|
onCopy={onCopy}
|
|
60
62
|
copied={copied}
|
|
61
|
-
endpointSlices={
|
|
62
|
-
endpointSlicesLoading={
|
|
63
|
+
endpointSlices={legacy ? matchingNamespaceSlices : serviceSlices.data?.items}
|
|
64
|
+
endpointSlicesLoading={legacy ? namespaceSlicesLoading : serviceSlices.isLoading}
|
|
65
|
+
endpointSlicesEnabled={!legacy && canListEndpointSlices}
|
|
66
|
+
endpointSlicesError={(legacy ? namespaceSlicesError : serviceSlices.error)?.message}
|
|
67
|
+
endpointSlicesTruncated={!legacy && serviceSlices.data?.truncated}
|
|
63
68
|
onNavigate={onNavigate}
|
|
64
69
|
renderPortAction={({ port, name, appProtocol, protocol }) => (
|
|
65
70
|
<>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import type { useArgoStatus } from '../../api/client'
|
|
4
|
+
import type { IntegrationProfile } from './LocalConnectionSettings'
|
|
5
|
+
import { LocalIntegrationStatus } from './LocalIntegrationStatus'
|
|
6
|
+
|
|
7
|
+
vi.mock('../../api/client', () => ({
|
|
8
|
+
usePrometheusStatus: () => ({}),
|
|
9
|
+
useOpenCostSummary: () => ({}),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
const profile: IntegrationProfile = {
|
|
13
|
+
target: {
|
|
14
|
+
binding: 'opaque',
|
|
15
|
+
context: 'development',
|
|
16
|
+
source: '/configs/team',
|
|
17
|
+
inFileName: 'dev',
|
|
18
|
+
fingerprint: 'target',
|
|
19
|
+
clientGeneration: 1,
|
|
20
|
+
operationGeneration: 1,
|
|
21
|
+
identity: { server: 'https://kubernetes', user: 'developer', trust: 'trust', insecureTls: false },
|
|
22
|
+
},
|
|
23
|
+
revision: 'revision',
|
|
24
|
+
state: 'saved',
|
|
25
|
+
mode: 'auto',
|
|
26
|
+
url: '',
|
|
27
|
+
headerKeys: [],
|
|
28
|
+
envHeaderKeys: [],
|
|
29
|
+
headersManaged: false,
|
|
30
|
+
secretSet: true,
|
|
31
|
+
insecureTls: false,
|
|
32
|
+
clusterId: '',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const render = (data: Record<string, unknown>) => {
|
|
36
|
+
const argo = { data, isError: false, isPlaceholderData: false, isFetching: false, dataUpdatedAt: 1, errorUpdatedAt: 0 } as unknown as ReturnType<typeof useArgoStatus>
|
|
37
|
+
return renderToStaticMarkup(<LocalIntegrationStatus kind="argocd" profile={profile} argo={argo} busy={false} changedAt={0} />)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('LocalIntegrationStatus for Argo CD', () => {
|
|
41
|
+
it('says Connecting while the first connection is being made', () => {
|
|
42
|
+
const html = render({ configured: true, connected: false, connecting: true })
|
|
43
|
+
expect(html).toContain('Connecting…')
|
|
44
|
+
expect(html).not.toContain('Not connected')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('shows why the last read failed on a working connection', () => {
|
|
48
|
+
const html = render({ configured: true, connected: true, address: 'https://argocd', reason: "Radar's last read from Argo CD failed: the request timed out." })
|
|
49
|
+
expect(html).toContain('Connected')
|
|
50
|
+
expect(html).toContain('the request timed out')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -26,13 +26,12 @@ export function LocalIntegrationStatus({ kind, profile, argo, busy, changedAt }:
|
|
|
26
26
|
status = prom.data.connected ? 'Connected' : prom.data.discovering ? 'Discovering…' : 'Not connected'
|
|
27
27
|
detail = prom.data.connected ? !profile.url ? `Discovered: ${prom.data.address}` : undefined : prom.data.error
|
|
28
28
|
} else if (kind === 'argocd' && argo.data) {
|
|
29
|
-
status = argo.data.connected ? argo.data.anonymous ? 'Connected · no token needed' : 'Connected' : 'Not connected'
|
|
30
|
-
detail = argo.data.connected
|
|
29
|
+
status = argo.data.connected ? argo.data.anonymous ? 'Connected · no token needed' : 'Connected' : argo.data.connecting ? 'Connecting…' : 'Not connected'
|
|
30
|
+
detail = argo.data.reason ?? (argo.data.connected && !profile.url ? `Discovered: ${argo.data.address}` : undefined)
|
|
31
31
|
} else if (kind === 'cost' && cost.data) {
|
|
32
32
|
status = cost.data.available ? `Available · ${costSourceLabel(cost.data.source)}`
|
|
33
33
|
: ['no_prometheus', 'no_cost_source', 'no_metrics'].includes(cost.data.reason ?? '') ? 'No cost data' : 'Unavailable'
|
|
34
|
-
detail = cost.data.reason ? costIntegrationUnavailableMessage(cost.data.reason)
|
|
35
|
-
?? (cost.data.reason === 'query_error' ? 'The active cost source query failed.' : undefined) : undefined
|
|
34
|
+
detail = cost.data.reason ? costIntegrationUnavailableMessage(cost.data.reason) : undefined
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
const lastDetail = useRef({ text: detail, visible: !!detail })
|