@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
|
@@ -75,7 +75,8 @@ export function HelmCompareRoute() {
|
|
|
75
75
|
const right = revisions.find((r) => r.revision === revision2)
|
|
76
76
|
|
|
77
77
|
const manifestDiff = useHelmManifestDiff(helmNamespace, releaseName, revision1, revision2, pairReady)
|
|
78
|
-
const
|
|
78
|
+
const overridesDiff = useHelmValuesDiff(helmNamespace, releaseName, revision1, revision2, false, pairReady)
|
|
79
|
+
const effectiveValuesDiff = useHelmValuesDiff(helmNamespace, releaseName, revision1, revision2, true, pairReady)
|
|
79
80
|
const notesDiff = useHelmNotesDiff(helmNamespace, releaseName, revision1, revision2, pairReady)
|
|
80
81
|
const hooksDiff = useHelmHooksDiff(helmNamespace, releaseName, revision1, revision2, pairReady)
|
|
81
82
|
const resourceDiff = useHelmResourceDiff(helmNamespace, releaseName, revision1, revision2, pairReady)
|
|
@@ -219,7 +220,8 @@ export function HelmCompareRoute() {
|
|
|
219
220
|
['summary', 'Summary'],
|
|
220
221
|
['manifest', 'Manifest'],
|
|
221
222
|
['resources', 'Resources'],
|
|
222
|
-
['values', 'Values'],
|
|
223
|
+
['effective-values', 'Effective Values'],
|
|
224
|
+
['overrides', 'User Overrides'],
|
|
223
225
|
['hooks', 'Hooks'],
|
|
224
226
|
['notes', 'Notes'],
|
|
225
227
|
].map(([id, label]) => (
|
|
@@ -256,9 +258,12 @@ export function HelmCompareRoute() {
|
|
|
256
258
|
manifestDiff={manifestDiff.data?.diff}
|
|
257
259
|
manifestLoading={manifestDiff.isLoading}
|
|
258
260
|
manifestError={manifestDiff.error}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
overridesDiff={overridesDiff.data?.diff}
|
|
262
|
+
overridesLoading={overridesDiff.isLoading}
|
|
263
|
+
overridesError={overridesDiff.error}
|
|
264
|
+
effectiveValuesDiff={effectiveValuesDiff.data?.diff}
|
|
265
|
+
effectiveValuesLoading={effectiveValuesDiff.isLoading}
|
|
266
|
+
effectiveValuesError={effectiveValuesDiff.error}
|
|
262
267
|
notesDiff={notesDiff.data?.diff}
|
|
263
268
|
notesLoading={notesDiff.isLoading}
|
|
264
269
|
notesError={notesDiff.error}
|
|
@@ -293,14 +298,25 @@ export function HelmCompareRoute() {
|
|
|
293
298
|
/>
|
|
294
299
|
|
|
295
300
|
<DiffSection
|
|
296
|
-
id="values"
|
|
301
|
+
id="effective-values"
|
|
297
302
|
icon={Settings}
|
|
298
|
-
title="
|
|
299
|
-
description="
|
|
300
|
-
diff={
|
|
301
|
-
isLoading={
|
|
302
|
-
error={
|
|
303
|
-
emptyLabel="No
|
|
303
|
+
title="Effective values diff"
|
|
304
|
+
description="Computed values for each revision are compared here, including that revision's chart defaults and user overrides."
|
|
305
|
+
diff={effectiveValuesDiff.data?.diff || ''}
|
|
306
|
+
isLoading={effectiveValuesDiff.isLoading}
|
|
307
|
+
error={effectiveValuesDiff.error}
|
|
308
|
+
emptyLabel="No effective value changes found."
|
|
309
|
+
/>
|
|
310
|
+
|
|
311
|
+
<DiffSection
|
|
312
|
+
id="overrides"
|
|
313
|
+
icon={Settings}
|
|
314
|
+
title="User overrides diff"
|
|
315
|
+
description="Only values explicitly supplied to each release revision are compared here."
|
|
316
|
+
diff={overridesDiff.data?.diff || ''}
|
|
317
|
+
isLoading={overridesDiff.isLoading}
|
|
318
|
+
error={overridesDiff.error}
|
|
319
|
+
emptyLabel="No user override changes found."
|
|
304
320
|
/>
|
|
305
321
|
|
|
306
322
|
<HooksDiffSection diff={hooksDiff.data} isLoading={hooksDiff.isLoading} error={hooksDiff.error} />
|
|
@@ -485,7 +501,7 @@ function formatRevisionOption(revision: HelmRevision): string {
|
|
|
485
501
|
return `rev ${revision.revision} - ${revision.status} - ${revision.chart}`
|
|
486
502
|
}
|
|
487
503
|
|
|
488
|
-
function CompareSummary({
|
|
504
|
+
export function CompareSummary({
|
|
489
505
|
left,
|
|
490
506
|
right,
|
|
491
507
|
revision1,
|
|
@@ -493,9 +509,12 @@ function CompareSummary({
|
|
|
493
509
|
manifestDiff,
|
|
494
510
|
manifestLoading,
|
|
495
511
|
manifestError,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
512
|
+
overridesDiff,
|
|
513
|
+
overridesLoading,
|
|
514
|
+
overridesError,
|
|
515
|
+
effectiveValuesDiff,
|
|
516
|
+
effectiveValuesLoading,
|
|
517
|
+
effectiveValuesError,
|
|
499
518
|
notesDiff,
|
|
500
519
|
notesLoading,
|
|
501
520
|
notesError,
|
|
@@ -513,9 +532,12 @@ function CompareSummary({
|
|
|
513
532
|
manifestDiff?: string
|
|
514
533
|
manifestLoading: boolean
|
|
515
534
|
manifestError: unknown
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
535
|
+
overridesDiff?: string
|
|
536
|
+
overridesLoading: boolean
|
|
537
|
+
overridesError: unknown
|
|
538
|
+
effectiveValuesDiff?: string
|
|
539
|
+
effectiveValuesLoading: boolean
|
|
540
|
+
effectiveValuesError: unknown
|
|
519
541
|
notesDiff?: string
|
|
520
542
|
notesLoading: boolean
|
|
521
543
|
notesError: unknown
|
|
@@ -527,7 +549,8 @@ function CompareSummary({
|
|
|
527
549
|
resourceError: unknown
|
|
528
550
|
}) {
|
|
529
551
|
const manifestStats = diffStats(manifestDiff || '')
|
|
530
|
-
const
|
|
552
|
+
const overridesStats = diffStats(overridesDiff || '')
|
|
553
|
+
const effectiveValuesStats = diffStats(effectiveValuesDiff || '')
|
|
531
554
|
const notesStats = diffStats(notesDiff || '')
|
|
532
555
|
const hookChanged = hooksDiff ? hooksDiff.added.length + hooksDiff.removed.length + hooksDiff.modified.length : 0
|
|
533
556
|
const resourceChanged = resourceDiff ? resourceDiff.added.length + resourceDiff.removed.length + resourceDiff.modified.length : 0
|
|
@@ -566,12 +589,20 @@ function CompareSummary({
|
|
|
566
589
|
sectionId="resources"
|
|
567
590
|
/>
|
|
568
591
|
<SignalPill
|
|
569
|
-
label="
|
|
570
|
-
loading={
|
|
571
|
-
error={
|
|
572
|
-
tone={
|
|
573
|
-
value={
|
|
574
|
-
sectionId="values"
|
|
592
|
+
label="Effective values"
|
|
593
|
+
loading={effectiveValuesLoading}
|
|
594
|
+
error={effectiveValuesError}
|
|
595
|
+
tone={effectiveValuesStats.changed ? 'info' : 'neutral'}
|
|
596
|
+
value={effectiveValuesStats.changed ? `${effectiveValuesStats.additions} add / ${effectiveValuesStats.removals} remove` : 'same'}
|
|
597
|
+
sectionId="effective-values"
|
|
598
|
+
/>
|
|
599
|
+
<SignalPill
|
|
600
|
+
label="Overrides"
|
|
601
|
+
loading={overridesLoading}
|
|
602
|
+
error={overridesError}
|
|
603
|
+
tone={overridesStats.changed ? 'info' : 'neutral'}
|
|
604
|
+
value={overridesStats.changed ? `${overridesStats.additions} add / ${overridesStats.removals} remove` : 'same'}
|
|
605
|
+
sectionId="overrides"
|
|
575
606
|
/>
|
|
576
607
|
<SignalPill
|
|
577
608
|
label="Hooks"
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { isUpgradeSourceIssueActionable } from './HelmReleaseDrawer'
|
|
2
|
+
import { DEFAULT_SHOW_EFFECTIVE_VALUES, effectiveHelmStorageNamespace, isUpgradeSourceIssueActionable, upgradePickerVersions } from './HelmReleaseDrawer'
|
|
3
|
+
|
|
4
|
+
describe('Helm values view', () => {
|
|
5
|
+
it('opens on user overrides', () => {
|
|
6
|
+
expect(DEFAULT_SHOW_EFFECTIVE_VALUES).toBe(false)
|
|
7
|
+
})
|
|
8
|
+
})
|
|
3
9
|
|
|
4
10
|
describe('isUpgradeSourceIssueActionable', () => {
|
|
5
|
-
it('
|
|
6
|
-
expect(isUpgradeSourceIssueActionable('ambiguous_repository')).toBe(
|
|
11
|
+
it('allows explicit association for ambiguous and unavailable sources', () => {
|
|
12
|
+
expect(isUpgradeSourceIssueActionable('ambiguous_repository')).toBe(true)
|
|
13
|
+
expect(isUpgradeSourceIssueActionable('ambiguous_source')).toBe(true)
|
|
14
|
+
expect(isUpgradeSourceIssueActionable('source_unavailable')).toBe(true)
|
|
7
15
|
})
|
|
8
16
|
|
|
9
17
|
it('lets OCI registration help untracked and repo-index states', () => {
|
|
@@ -15,3 +23,47 @@ describe('isUpgradeSourceIssueActionable', () => {
|
|
|
15
23
|
expect(isUpgradeSourceIssueActionable(undefined)).toBe(false)
|
|
16
24
|
})
|
|
17
25
|
})
|
|
26
|
+
|
|
27
|
+
describe('effectiveHelmStorageNamespace', () => {
|
|
28
|
+
it('falls back to the release namespace when storageNamespace is empty', () => {
|
|
29
|
+
expect(effectiveHelmStorageNamespace({ namespace: 'production', name: 'release', storageNamespace: '' })).toBe('production')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('uses an explicit storage namespace', () => {
|
|
33
|
+
expect(effectiveHelmStorageNamespace({ namespace: 'production', name: 'release', storageNamespace: 'helm-storage' })).toBe('helm-storage')
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('upgradePickerVersions', () => {
|
|
38
|
+
it('lists the repository versions when the installed one is still published', () => {
|
|
39
|
+
expect(upgradePickerVersions(['1.5.0', '1.4.0'], '1.4.0')).toEqual([
|
|
40
|
+
{ version: '1.5.0', selectable: true },
|
|
41
|
+
{ version: '1.4.0', selectable: true },
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('shows a dropped installed version in order but not selectable', () => {
|
|
46
|
+
expect(upgradePickerVersions(['1.6.0', '1.5.0', '1.3.0'], '1.4.0')).toEqual([
|
|
47
|
+
{ version: '1.6.0', selectable: true },
|
|
48
|
+
{ version: '1.5.0', selectable: true },
|
|
49
|
+
{ version: '1.4.0', selectable: false },
|
|
50
|
+
{ version: '1.3.0', selectable: true },
|
|
51
|
+
])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('offers a picker even when only one newer version is left', () => {
|
|
55
|
+
expect(upgradePickerVersions(['1.5.0'], '1.4.0')).toEqual([
|
|
56
|
+
{ version: '1.5.0', selectable: true },
|
|
57
|
+
{ version: '1.4.0', selectable: false },
|
|
58
|
+
])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('puts a dropped version older than every listed one at the end', () => {
|
|
62
|
+
expect(upgradePickerVersions(['2.1.0', '2.0.0'], '1.0.0')?.at(-1)).toEqual({ version: '1.0.0', selectable: false })
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('has nothing to pick with a single published version that is the installed one', () => {
|
|
66
|
+
expect(upgradePickerVersions(['1.5.0'], '1.5.0')).toBeNull()
|
|
67
|
+
expect(upgradePickerVersions(undefined, '1.5.0')).toBeNull()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -37,6 +37,10 @@ interface HelmReleaseDrawerProps {
|
|
|
37
37
|
rightInset?: number
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export function effectiveHelmStorageNamespace(release: SelectedHelmRelease): string {
|
|
41
|
+
return release.storageNamespace || release.namespace
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
type TabId = 'overview' | 'history' | 'manifest' | 'values' | 'resources' | 'hooks'
|
|
41
45
|
|
|
42
46
|
interface UpgradePreviewRequest {
|
|
@@ -51,7 +55,7 @@ interface ParsedUpgradeValues {
|
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
type UpgradeSourceIssue = NonNullable<UpgradeInfo['sourceIssue']>
|
|
54
|
-
type ActionableUpgradeSourceIssue =
|
|
58
|
+
type ActionableUpgradeSourceIssue = UpgradeSourceIssue
|
|
55
59
|
|
|
56
60
|
function getUpgradeSourceIssue(upgradeInfo: UpgradeInfo): UpgradeSourceIssue | undefined {
|
|
57
61
|
return upgradeInfo.sourceIssue
|
|
@@ -63,6 +67,10 @@ function getUpgradeSourceIssueLabel(issue: UpgradeSourceIssue) {
|
|
|
63
67
|
return 'upgrade source blocked'
|
|
64
68
|
case 'ambiguous_repository':
|
|
65
69
|
return 'upgrade source ambiguous'
|
|
70
|
+
case 'ambiguous_source':
|
|
71
|
+
return 'upgrade source ambiguous'
|
|
72
|
+
case 'source_unavailable':
|
|
73
|
+
return 'recorded source unavailable'
|
|
66
74
|
case 'untracked':
|
|
67
75
|
return 'upgrade source not tracked'
|
|
68
76
|
}
|
|
@@ -77,6 +85,10 @@ function getUpgradeSourceIssueTooltip(issue: UpgradeSourceIssue, error: string |
|
|
|
77
85
|
return 'A configured Helm repo index failed. Fix or refresh that repo, or register an OCI prefix if this chart came from OCI.'
|
|
78
86
|
case 'ambiguous_repository':
|
|
79
87
|
return 'Multiple configured Helm repos match this chart. Fix the repo list or source metadata so Radar can identify one source.'
|
|
88
|
+
case 'ambiguous_source':
|
|
89
|
+
return 'Multiple configured sources match this chart. Select the original source explicitly.'
|
|
90
|
+
case 'source_unavailable':
|
|
91
|
+
return 'The recorded source is not configured or cannot verify the installed chart version on this Radar installation.'
|
|
80
92
|
case 'untracked':
|
|
81
93
|
return "Radar can't tell where this chart was installed from. Register an OCI chart source to track upgrades."
|
|
82
94
|
}
|
|
@@ -96,12 +108,34 @@ function parseUpgradeValuesYaml(raw: string): ParsedUpgradeValues {
|
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
export function isUpgradeSourceIssueActionable(issue: UpgradeInfo['sourceIssue']): issue is ActionableUpgradeSourceIssue {
|
|
99
|
-
return Boolean(issue
|
|
111
|
+
return Boolean(issue)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface UpgradePickerVersion {
|
|
115
|
+
version: string
|
|
116
|
+
// False for the installed version once the repository no longer publishes it:
|
|
117
|
+
// it is shown for orientation but can't be picked.
|
|
118
|
+
selectable: boolean
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Versions for the upgrade picker, newest first as the server returns them.
|
|
122
|
+
// When the repository dropped the installed version, it is slotted in by
|
|
123
|
+
// version order as a non-selectable entry so the user still sees where they are.
|
|
124
|
+
// Returns null when there is nothing to choose between.
|
|
125
|
+
export function upgradePickerVersions(available: string[] | undefined, current: string | undefined): UpgradePickerVersion[] | null {
|
|
126
|
+
if (!available) return null
|
|
127
|
+
const entries: UpgradePickerVersion[] = available.map(version => ({ version, selectable: true }))
|
|
128
|
+
if (current && !available.includes(current)) {
|
|
129
|
+
const olderIndex = available.findIndex(v => compareVersions(v, current) === -1)
|
|
130
|
+
entries.splice(olderIndex === -1 ? entries.length : olderIndex, 0, { version: current, selectable: false })
|
|
131
|
+
}
|
|
132
|
+
return entries.length > 1 ? entries : null
|
|
100
133
|
}
|
|
101
134
|
|
|
102
135
|
const MIN_WIDTH = 500
|
|
103
136
|
const MAX_WIDTH_PERCENT = 0.8
|
|
104
137
|
const DEFAULT_WIDTH = 1000
|
|
138
|
+
export const DEFAULT_SHOW_EFFECTIVE_VALUES = false
|
|
105
139
|
|
|
106
140
|
export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOpen = true, rightInset = 0 }: HelmReleaseDrawerProps) {
|
|
107
141
|
const navigate = useNavigate()
|
|
@@ -111,7 +145,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
111
145
|
const [viewportWidth, setViewportWidth] = useState(() => (typeof window === 'undefined' ? DEFAULT_WIDTH : window.innerWidth))
|
|
112
146
|
const [isResizing, setIsResizing] = useState(false)
|
|
113
147
|
const [selectedRevision, setSelectedRevision] = useState<number | undefined>(undefined)
|
|
114
|
-
const [
|
|
148
|
+
const [showEffectiveValues, setShowEffectiveValues] = useState(DEFAULT_SHOW_EFFECTIVE_VALUES)
|
|
115
149
|
const [rollbackRevision, setRollbackRevision] = useState<number | null>(null)
|
|
116
150
|
const [showUninstallConfirm, setShowUninstallConfirm] = useState(false)
|
|
117
151
|
const [showUpgradeConfirm, setShowUpgradeConfirm] = useState(false)
|
|
@@ -128,7 +162,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
128
162
|
const resizeStartWidth = useRef(DEFAULT_WIDTH)
|
|
129
163
|
const targetVersionRef = useRef('')
|
|
130
164
|
const editedUpgradeYamlRef = useRef('')
|
|
131
|
-
const helmNamespace = release
|
|
165
|
+
const helmNamespace = effectiveHelmStorageNamespace(release)
|
|
132
166
|
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct(helmNamespace)
|
|
133
167
|
|
|
134
168
|
const { data: releaseDetail, isLoading, error: releaseError, refetch: refetchRelease } = useHelmRelease(
|
|
@@ -145,10 +179,10 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
145
179
|
)
|
|
146
180
|
|
|
147
181
|
// Fetch values
|
|
148
|
-
const { data: values, isLoading: valuesLoading } = useHelmValues(
|
|
182
|
+
const { data: values, isLoading: valuesLoading, error: valuesError, refetch: refetchValues } = useHelmValues(
|
|
149
183
|
helmNamespace,
|
|
150
184
|
release.name,
|
|
151
|
-
|
|
185
|
+
true,
|
|
152
186
|
true,
|
|
153
187
|
selectedRevision,
|
|
154
188
|
)
|
|
@@ -179,7 +213,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
179
213
|
error: availableVersionsError,
|
|
180
214
|
} = useHelmReleaseVersions(helmNamespace, release.name, showUpgradeConfirm)
|
|
181
215
|
const targetVersion = selectedVersion ?? upgradeInfo?.latestVersion ?? ''
|
|
182
|
-
const selectableVersions = availableVersions
|
|
216
|
+
const selectableVersions = upgradePickerVersions(availableVersions, upgradeInfo?.currentVersion)
|
|
183
217
|
// Semver compare, not list-position: the installed version may be older than
|
|
184
218
|
// the newest-N versions the picker shows, so it isn't always in the list.
|
|
185
219
|
const isDowngrade = Boolean(
|
|
@@ -735,8 +769,9 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
735
769
|
<ValuesViewer
|
|
736
770
|
values={values}
|
|
737
771
|
isLoading={valuesLoading}
|
|
738
|
-
|
|
739
|
-
|
|
772
|
+
error={valuesError}
|
|
773
|
+
showEffectiveValues={showEffectiveValues}
|
|
774
|
+
onToggleEffectiveValues={setShowEffectiveValues}
|
|
740
775
|
onCopy={(text) => copyToClipboard(text, 'values')}
|
|
741
776
|
copied={copied === 'values'}
|
|
742
777
|
namespace={helmNamespace}
|
|
@@ -744,6 +779,7 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
744
779
|
revision={selectedRevision}
|
|
745
780
|
currentRevision={releaseDetail.revision}
|
|
746
781
|
onApplySuccess={() => refetch()}
|
|
782
|
+
onRetry={() => void refetchValues()}
|
|
747
783
|
/>
|
|
748
784
|
)}
|
|
749
785
|
{activeTab === 'resources' && (
|
|
@@ -853,11 +889,11 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
853
889
|
disabled={isUpgrading || isPreviewingUpgrade}
|
|
854
890
|
className="w-full px-3 py-2 bg-theme-elevated border border-theme-border-light rounded-lg text-sm text-theme-text-primary focus:outline-none focus:ring-2 focus:ring-accent disabled:opacity-50"
|
|
855
891
|
>
|
|
856
|
-
{selectableVersions.map((v) => (
|
|
857
|
-
<option key={v} value={v}>
|
|
892
|
+
{selectableVersions.map(({ version: v, selectable }) => (
|
|
893
|
+
<option key={v} value={v} disabled={!selectable}>
|
|
858
894
|
{v}
|
|
859
895
|
{v === upgradeInfo?.latestVersion ? ' (latest)' : ''}
|
|
860
|
-
{v === upgradeInfo?.currentVersion ? ' (current)' : ''}
|
|
896
|
+
{v === upgradeInfo?.currentVersion ? (selectable ? ' (current)' : ' (current, no longer in the repository)') : ''}
|
|
861
897
|
</option>
|
|
862
898
|
))}
|
|
863
899
|
</select>
|
|
@@ -986,6 +1022,8 @@ export function HelmReleaseDrawer({ release, onClose, onNavigateToResource, isOp
|
|
|
986
1022
|
open={showTrackSource}
|
|
987
1023
|
onClose={() => setShowTrackSource(false)}
|
|
988
1024
|
chartName={releaseDetail?.chart}
|
|
1025
|
+
namespace={helmNamespace}
|
|
1026
|
+
releaseName={release.name}
|
|
989
1027
|
sourceIssue={upgradeSourceIssue}
|
|
990
1028
|
sourceError={upgradeInfo?.error}
|
|
991
1029
|
/>
|
|
@@ -2,9 +2,10 @@ import { useState } from 'react'
|
|
|
2
2
|
import { History, Eye, GitCompare, Check, RotateCcw } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import type { HelmOperation, HelmRevision } from '../../types'
|
|
5
|
-
import { getStatusColor, formatDate
|
|
5
|
+
import { getStatusColor, formatDate } from './helm-utils'
|
|
6
6
|
import { SEVERITY_BADGE } from '../../utils/badge-colors'
|
|
7
7
|
import { Tooltip } from '../ui/Tooltip'
|
|
8
|
+
import { formatRelativeAgeTime } from '@skyhook-io/k8s-ui/utils/format'
|
|
8
9
|
|
|
9
10
|
interface RevisionHistoryProps {
|
|
10
11
|
history: HelmRevision[]
|
|
@@ -117,7 +118,7 @@ export function RevisionHistory({ history, currentRevision, operations = [], onV
|
|
|
117
118
|
|
|
118
119
|
<div className="flex items-center gap-4 mt-1 text-xs text-theme-text-tertiary">
|
|
119
120
|
<Tooltip content={revision.updated}><span>{formatDate(revision.updated)}</span></Tooltip>
|
|
120
|
-
<span>{
|
|
121
|
+
<span>{formatRelativeAgeTime(revision.updated)}</span>
|
|
121
122
|
</div>
|
|
122
123
|
|
|
123
124
|
{revision.description && (
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, type InputHTMLAttributes, type ReactNode } from 'react'
|
|
3
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import type { ChartSourceStatus, HelmRepository } from '../../types'
|
|
6
|
+
import { TrackChartSourceDialog } from './TrackChartSourceDialog'
|
|
7
|
+
|
|
8
|
+
const state = vi.hoisted(() => ({
|
|
9
|
+
status: undefined as ChartSourceStatus | undefined,
|
|
10
|
+
repositories: [] as HelmRepository[],
|
|
11
|
+
isOwner: true,
|
|
12
|
+
addRepository: vi.fn(),
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
vi.mock('@skyhook-io/k8s-ui/components/ui/DialogPortal', () => ({
|
|
16
|
+
DialogPortal: ({ open, children }: { open: boolean; children: ReactNode }) => (open ? <div>{children}</div> : null),
|
|
17
|
+
}))
|
|
18
|
+
vi.mock('@skyhook-io/k8s-ui', () => ({
|
|
19
|
+
Input: (props: InputHTMLAttributes<HTMLInputElement>) => <input {...props} />,
|
|
20
|
+
}))
|
|
21
|
+
vi.mock('../settings/OperatorManagedNotice', () => ({ OperatorManagedNotice: () => null }))
|
|
22
|
+
vi.mock('../../api/client', () => {
|
|
23
|
+
const idle = { mutate: vi.fn(), mutateAsync: vi.fn(), isPending: false }
|
|
24
|
+
return {
|
|
25
|
+
useHelmSourceStatus: () => ({ data: state.status, error: null, refetch: vi.fn() }),
|
|
26
|
+
useHelmRepositories: () => ({ data: state.repositories, isLoading: false }),
|
|
27
|
+
useHelmOCISources: () => ({ data: [] }),
|
|
28
|
+
useClusterInfo: () => ({ data: { inCluster: false } }),
|
|
29
|
+
useCapabilities: () => ({ data: { configManagement: 'local' } }),
|
|
30
|
+
useCloudRole: () => ({ canAtLeast: () => state.isOwner }),
|
|
31
|
+
useAddHelmRepository: () => ({ mutate: state.addRepository, isPending: false }),
|
|
32
|
+
useAddOCISource: () => idle,
|
|
33
|
+
useRemoveOCISource: () => idle,
|
|
34
|
+
useSetHelmSource: () => idle,
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
|
39
|
+
|
|
40
|
+
const recordedURL = 'https://stefanprodan.github.io/podinfo'
|
|
41
|
+
const movedMachine: ChartSourceStatus = {
|
|
42
|
+
recorded: { type: 'repository', reference: 'podinfo', url: recordedURL },
|
|
43
|
+
configured: false,
|
|
44
|
+
available: false,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let root: Root
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
state.status = movedMachine
|
|
51
|
+
state.repositories = []
|
|
52
|
+
state.isOwner = true
|
|
53
|
+
state.addRepository.mockReset()
|
|
54
|
+
const element = document.createElement('div')
|
|
55
|
+
document.body.appendChild(element)
|
|
56
|
+
root = createRoot(element)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
afterEach(async () => {
|
|
60
|
+
await act(async () => root.unmount())
|
|
61
|
+
document.body.replaceChildren()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
async function renderDialog() {
|
|
65
|
+
await act(async () => root.render(<TrackChartSourceDialog open onClose={() => undefined} chartName="podinfo" namespace="web" releaseName="storefront" />))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function addRecordedButton() {
|
|
69
|
+
return Array.from(document.querySelectorAll('button')).find(button => button.textContent?.includes('and use it'))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe('TrackChartSourceDialog one-click add of the recorded repository', () => {
|
|
73
|
+
it('adds the recorded URL under the recorded name when that name is free', async () => {
|
|
74
|
+
await renderDialog()
|
|
75
|
+
const button = addRecordedButton()
|
|
76
|
+
expect(button?.textContent).toBe('Add stefanprodan.github.io/podinfo and use it')
|
|
77
|
+
expect(button?.disabled).toBe(false)
|
|
78
|
+
expect(document.body.textContent).toContain('Saved in Helm as podinfo, the name it was recorded under.')
|
|
79
|
+
|
|
80
|
+
await act(async () => button?.click())
|
|
81
|
+
expect(state.addRepository).toHaveBeenCalledWith(
|
|
82
|
+
{ name: 'podinfo', url: recordedURL, namespace: 'web', releaseName: 'storefront' },
|
|
83
|
+
expect.anything(),
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('never reuses a recorded name that points at a different repository here', async () => {
|
|
88
|
+
state.repositories = [
|
|
89
|
+
{ name: 'podinfo', url: 'https://impostor.example.test/charts' },
|
|
90
|
+
{ name: 'podinfo-2', url: 'https://other.example.test' },
|
|
91
|
+
]
|
|
92
|
+
await renderDialog()
|
|
93
|
+
expect(document.body.textContent).toContain('Saved in Helm as podinfo-3. The name podinfo already points to a different repository here.')
|
|
94
|
+
|
|
95
|
+
await act(async () => addRecordedButton()?.click())
|
|
96
|
+
expect(state.addRepository).toHaveBeenCalledWith(
|
|
97
|
+
{ name: 'podinfo-3', url: recordedURL, namespace: 'web', releaseName: 'storefront' },
|
|
98
|
+
expect.anything(),
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('is disabled for a non-owner, who is told who can add sources', async () => {
|
|
103
|
+
state.isOwner = false
|
|
104
|
+
await renderDialog()
|
|
105
|
+
expect(addRecordedButton()?.disabled).toBe(true)
|
|
106
|
+
expect(document.body.textContent).toContain('Only organization owners can add or remove chart sources.')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('is not offered when the recorded source is configured, OCI, or not recorded', async () => {
|
|
110
|
+
for (const status of [
|
|
111
|
+
{ ...movedMachine, configured: true },
|
|
112
|
+
{ recorded: { type: 'oci' as const, reference: 'oci://ghcr.io/example/charts' }, configured: false, available: false },
|
|
113
|
+
{ configured: false, available: false },
|
|
114
|
+
]) {
|
|
115
|
+
state.status = status
|
|
116
|
+
await renderDialog()
|
|
117
|
+
expect(addRecordedButton()).toBeUndefined()
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getSourceIssueCopy, isValidHelmRepositoryURL, normalizedRepositoryName, recordedRepositoryName, repositoryDisplayURL, sourceStateLabel, sourceStatusLine } from './TrackChartSourceDialog'
|
|
3
|
+
|
|
4
|
+
describe('chart source recovery presentation', () => {
|
|
5
|
+
it('keeps recorded, configured, and available states distinct', () => {
|
|
6
|
+
expect(sourceStateLabel(false, false, false)).toBe('Not recorded')
|
|
7
|
+
expect(sourceStateLabel(true, false, false)).toBe('Recorded · not configured here')
|
|
8
|
+
expect(sourceStateLabel(true, true, false)).toBe('Recorded and configured · unavailable or version missing')
|
|
9
|
+
expect(sourceStateLabel(true, true, true)).toBe('Recorded · configured · available')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('claims nothing about the recorded source until it has been read', () => {
|
|
13
|
+
expect(sourceStatusLine(undefined, undefined)).toBe('Checking the recorded source...')
|
|
14
|
+
expect(sourceStatusLine(undefined, new Error('failed to load repo file'))).toBe("Could not read this release's source: failed to load repo file")
|
|
15
|
+
expect(sourceStatusLine({ configured: false, available: false }, new Error('refetch failed'))).toBe('Not recorded')
|
|
16
|
+
expect(sourceStatusLine({ recorded: { type: 'repository', reference: 'repo', url: 'https://charts.example.test' }, configured: false, available: false }, undefined)).toBe('Recorded · not configured here')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('makes ambiguity explicit rather than selecting a source', () => {
|
|
20
|
+
expect(getSourceIssueCopy('ambiguous_source')?.body).toContain('will not guess')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('accepts only HTTP repository URLs', () => {
|
|
24
|
+
expect(isValidHelmRepositoryURL('https://charts.example.test')).toBe(true)
|
|
25
|
+
expect(isValidHelmRepositoryURL('oci://registry.example.test/charts')).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('picks a free local name for the recorded repository', () => {
|
|
29
|
+
expect(recordedRepositoryName('podinfo', [])).toBe('podinfo')
|
|
30
|
+
expect(recordedRepositoryName('podinfo', [{ name: 'Podinfo', url: 'https://impostor.example.test' }])).toBe('podinfo-2')
|
|
31
|
+
expect(recordedRepositoryName('podinfo', [{ name: 'podinfo', url: 'a' }, { name: 'podinfo-2', url: 'b' }])).toBe('podinfo-3')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('checks names the way the server stores them', () => {
|
|
35
|
+
expect(normalizedRepositoryName(' My Charts! ')).toBe('my-charts')
|
|
36
|
+
const alias40 = 'a'.repeat(40)
|
|
37
|
+
expect(recordedRepositoryName(alias40, [{ name: alias40, url: 'a' }])).toBe(`${'a'.repeat(38)}-2`)
|
|
38
|
+
// A longer alias is stored cut to 40 characters, so that is the name that collides.
|
|
39
|
+
const alias45 = `${'b'.repeat(40)}extra`
|
|
40
|
+
expect(recordedRepositoryName(alias45, [])).toBe('b'.repeat(40))
|
|
41
|
+
expect(recordedRepositoryName(alias45, [{ name: 'b'.repeat(40), url: 'a' }])).toBe(`${'b'.repeat(38)}-2`)
|
|
42
|
+
expect(recordedRepositoryName('My_Repo', [{ name: 'my_repo', url: 'a' }])).toBe('my_repo-2')
|
|
43
|
+
expect(recordedRepositoryName('___', [])).toBe('')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('shortens repository URLs for labels', () => {
|
|
47
|
+
expect(repositoryDisplayURL('https://stefanprodan.github.io/podinfo/')).toBe('stefanprodan.github.io/podinfo')
|
|
48
|
+
expect(repositoryDisplayURL('https://charts.example.test')).toBe('charts.example.test')
|
|
49
|
+
})
|
|
50
|
+
})
|