@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
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { Loader2, Sparkles } from "lucide-react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type DiagnoseSetup,
|
|
4
|
+
useDiagnose,
|
|
5
|
+
useDiagnoseLayout,
|
|
6
|
+
} from "./DiagnoseContext";
|
|
3
7
|
import { runTargetKey } from "./target";
|
|
4
8
|
import { Tooltip } from "../ui/Tooltip";
|
|
5
9
|
import type { RenderDiagnoseAction } from "../../context/DiagnoseCustomization";
|
|
6
10
|
|
|
7
11
|
// The per-resource AI entry point. It no longer owns a panel — it just dispatches
|
|
8
12
|
// to the single app-level AI surface (DiagnoseContext), opening a new investigation
|
|
9
|
-
// for this resource.
|
|
10
|
-
//
|
|
13
|
+
// for this resource. Hidden only where investigations can't run here; with no
|
|
14
|
+
// agent CLI it stays, and opens the setup notice. Hosts can override this slot
|
|
15
|
+
// with their own action.
|
|
11
16
|
//
|
|
12
17
|
// Adaptive by health: on a resource with a live problem it reads as a prominent
|
|
13
18
|
// "Investigate" action; when the resource is fine or health is unknown it shrinks
|
|
@@ -33,13 +38,13 @@ function DiagnoseResourceButton({
|
|
|
33
38
|
// Hidden only when the feature can't work here (auth/cloud/--no-mcp). When it's
|
|
34
39
|
// supported but no agent is installed we KEEP the button — clicking opens the
|
|
35
40
|
// setup notice so the feature is discoverable rather than silently absent.
|
|
36
|
-
if (d.setupState === "off") return null;
|
|
41
|
+
if (d.setupState === "off" || d.setupState === "unknown") return null;
|
|
37
42
|
const ready = d.available;
|
|
38
43
|
const problem = health === "problem";
|
|
39
44
|
const running =
|
|
40
45
|
ready && runningKeys.has(runTargetKey(kind, namespace, name, group ?? ""));
|
|
41
46
|
const tooltip = !ready
|
|
42
|
-
?
|
|
47
|
+
? notReadyTooltip(d)
|
|
43
48
|
: running
|
|
44
49
|
? `${d.agentLabel} is investigating this resource — click to watch it live.`
|
|
45
50
|
: d.hosted
|
|
@@ -67,7 +72,7 @@ function DiagnoseResourceButton({
|
|
|
67
72
|
}
|
|
68
73
|
aria-label={
|
|
69
74
|
!ready
|
|
70
|
-
?
|
|
75
|
+
? notReadyTooltip(d)
|
|
71
76
|
: running
|
|
72
77
|
? "Investigation running — click to view"
|
|
73
78
|
: problem
|
|
@@ -122,18 +127,16 @@ export function IssueDiagnoseButton({
|
|
|
122
127
|
name: string;
|
|
123
128
|
}) {
|
|
124
129
|
const d = useDiagnose();
|
|
125
|
-
if (d.setupState === "off") return null;
|
|
130
|
+
if (d.setupState === "off" || d.setupState === "unknown") return null;
|
|
126
131
|
const ready = d.available;
|
|
127
132
|
return (
|
|
128
133
|
<Tooltip
|
|
129
134
|
content={
|
|
130
|
-
|
|
131
|
-
?
|
|
132
|
-
:
|
|
133
|
-
?
|
|
134
|
-
: d.
|
|
135
|
-
? `Sends this resource's context to ${d.agentLabel} for investigation`
|
|
136
|
-
: `Runs ${d.agentLabel} on your machine to investigate this resource`
|
|
135
|
+
!ready
|
|
136
|
+
? notReadyTooltip(d)
|
|
137
|
+
: d.hosted
|
|
138
|
+
? `Sends this resource's context to ${d.agentLabel} for investigation`
|
|
139
|
+
: `Runs ${d.agentLabel} on your machine to investigate this resource`
|
|
137
140
|
}
|
|
138
141
|
position="left"
|
|
139
142
|
>
|
|
@@ -158,12 +161,27 @@ export function IssueDiagnoseButton({
|
|
|
158
161
|
);
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
// The tooltip every AI entry point shows while investigations aren't runnable.
|
|
165
|
+
// It gives the same reason as the setup notice the button opens.
|
|
166
|
+
function notReadyTooltip(d: {
|
|
167
|
+
cliOverride: boolean;
|
|
168
|
+
setupState: DiagnoseSetup;
|
|
169
|
+
}): string {
|
|
170
|
+
if (d.cliOverride) {
|
|
171
|
+
return "AI investigations are off: can't run RADAR_AI_CLI_BIN";
|
|
172
|
+
}
|
|
173
|
+
if (d.setupState === "needs-restart") {
|
|
174
|
+
return "AI investigations aren't available right now";
|
|
175
|
+
}
|
|
176
|
+
return "Set up AI investigations";
|
|
177
|
+
}
|
|
178
|
+
|
|
161
179
|
// Global top-bar entry into the AI surface (opens its Home / recent
|
|
162
|
-
// investigations).
|
|
180
|
+
// investigations). Hidden only where investigations can't run here.
|
|
163
181
|
export function GlobalDiagnoseButton() {
|
|
164
182
|
const d = useDiagnose();
|
|
165
183
|
const { runningCount } = useDiagnoseLayout();
|
|
166
|
-
if (d.setupState === "off") return null;
|
|
184
|
+
if (d.setupState === "off" || d.setupState === "unknown") return null;
|
|
167
185
|
const ready = d.available;
|
|
168
186
|
const agentSuffix = d.hosted
|
|
169
187
|
? `powered by ${d.agentLabel}`
|
|
@@ -172,7 +190,7 @@ export function GlobalDiagnoseButton() {
|
|
|
172
190
|
<Tooltip
|
|
173
191
|
content={
|
|
174
192
|
!ready
|
|
175
|
-
?
|
|
193
|
+
? notReadyTooltip(d)
|
|
176
194
|
: runningCount > 0
|
|
177
195
|
? `${runningCount} investigation${runningCount > 1 ? "s" : ""} running — ${agentSuffix}`
|
|
178
196
|
: `AI investigations — ${agentSuffix}`
|
|
@@ -183,9 +201,11 @@ export function GlobalDiagnoseButton() {
|
|
|
183
201
|
onClick={() => d.openWorkspace()}
|
|
184
202
|
className="relative rounded-md bg-theme-elevated p-1.5 text-theme-text-secondary transition-colors hover:bg-theme-hover hover:text-theme-text-primary"
|
|
185
203
|
aria-label={
|
|
186
|
-
|
|
187
|
-
?
|
|
188
|
-
:
|
|
204
|
+
!ready
|
|
205
|
+
? notReadyTooltip(d)
|
|
206
|
+
: runningCount > 0
|
|
207
|
+
? `AI investigations (${runningCount} running)`
|
|
208
|
+
: "AI investigations"
|
|
189
209
|
}
|
|
190
210
|
>
|
|
191
211
|
<Sparkles className="h-4 w-4 text-accent" />
|
|
@@ -13,7 +13,7 @@ export const SUPPORTED_AGENTS: AgentInstall[] = [
|
|
|
13
13
|
name: "claude",
|
|
14
14
|
label: "Claude Code",
|
|
15
15
|
install: "npm install -g @anthropic-ai/claude-code",
|
|
16
|
-
docs: "https://
|
|
16
|
+
docs: "https://code.claude.com/docs/en/setup",
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
name: "codex",
|
|
@@ -25,7 +25,7 @@ export const SUPPORTED_AGENTS: AgentInstall[] = [
|
|
|
25
25
|
name: "cursor-agent",
|
|
26
26
|
label: "Cursor CLI",
|
|
27
27
|
install: "curl https://cursor.com/install -fsS | bash",
|
|
28
|
-
docs: "https://
|
|
28
|
+
docs: "https://cursor.com/docs/cli/overview",
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
name: "opencode",
|
|
@@ -93,6 +93,8 @@ export interface DiagnosisResourceContext {
|
|
|
93
93
|
};
|
|
94
94
|
runsOn?: DiagnosisResourceRef;
|
|
95
95
|
scaledBy?: DiagnosisScalerRef[];
|
|
96
|
+
dependencies?: DiagnosisResourceRef[];
|
|
97
|
+
dependents?: DiagnosisResourceRef[];
|
|
96
98
|
statusSummary?: {
|
|
97
99
|
phase?: string;
|
|
98
100
|
conditions?: Array<{
|
|
@@ -71,6 +71,14 @@ export function adaptIssues(
|
|
|
71
71
|
"truncated",
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
|
+
if (nonEmptyString(value.recent_changes_error)) {
|
|
75
|
+
builder.limit(
|
|
76
|
+
source,
|
|
77
|
+
"Issue-related changes",
|
|
78
|
+
value.recent_changes_error,
|
|
79
|
+
"error",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
74
82
|
if (value.correlation_truncated === true) {
|
|
75
83
|
builder.limit(
|
|
76
84
|
source,
|
|
@@ -247,6 +247,22 @@ describe("strict evidence adapters", () => {
|
|
|
247
247
|
}),
|
|
248
248
|
]);
|
|
249
249
|
|
|
250
|
+
const unreadIssueFeed = project([
|
|
251
|
+
tool("issues-down", "issues", {
|
|
252
|
+
issues: [],
|
|
253
|
+
total: 0,
|
|
254
|
+
total_matched: 0,
|
|
255
|
+
recent_changes_error: "timeline history is unavailable",
|
|
256
|
+
}),
|
|
257
|
+
]);
|
|
258
|
+
expect(unreadIssueFeed.limitations).toEqual([
|
|
259
|
+
expect.objectContaining({
|
|
260
|
+
source: "Issue-related changes",
|
|
261
|
+
kind: "error",
|
|
262
|
+
message: "timeline history is unavailable",
|
|
263
|
+
}),
|
|
264
|
+
]);
|
|
265
|
+
|
|
250
266
|
const filtered = project([
|
|
251
267
|
tool("filtered", "get_resource", {
|
|
252
268
|
resource: deployment,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { renderToString } from 'react-dom/server'
|
|
2
|
+
import { expect, it, vi } from 'vitest'
|
|
3
|
+
import type { TerminalTabProps } from '@skyhook-io/k8s-ui'
|
|
4
|
+
import { TerminalTab } from './TerminalTab'
|
|
5
|
+
const captured = vi.hoisted(() => ({ props: null as TerminalTabProps | null }))
|
|
6
|
+
vi.mock('@skyhook-io/k8s-ui', () => ({ TerminalTab: (props: TerminalTabProps) => { captured.props = props; return null } }))
|
|
7
|
+
vi.mock('../../api/config', () => ({ apiUrl: (path: string) => `/api${path}`, getWsUrl: (path: string) => `ws://localhost/api${path}` }))
|
|
8
|
+
it.each([undefined, '/bin/custom+command'])('forwards the executable and context through the shared terminal (%s)', async shell => {
|
|
9
|
+
renderToString(<TerminalTab namespace="app" podName="worker" containerName="app" containers={['app', 'sidecar']} shell={shell} note="Primary" />)
|
|
10
|
+
const props = captured.props!
|
|
11
|
+
expect(props.command).toBe(shell); expect(props.note).toBe('Primary')
|
|
12
|
+
expect(Boolean(props.createDebugContainer)).toBe(!shell)
|
|
13
|
+
const { wsUrl } = await props.createSession('app')
|
|
14
|
+
const url = new URL(wsUrl)
|
|
15
|
+
expect(url.pathname).toBe('/api/pods/app/worker/exec'); expect(url.searchParams.get('container')).toBe('app')
|
|
16
|
+
expect(url.searchParams.get('shell')).toBe(shell ?? null)
|
|
17
|
+
})
|
|
@@ -8,12 +8,14 @@ interface TerminalTabProps {
|
|
|
8
8
|
containerName: string
|
|
9
9
|
containers: string[]
|
|
10
10
|
isActive?: boolean
|
|
11
|
+
shell?: string
|
|
12
|
+
note?: string
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
export function TerminalTab({ namespace, podName, containerName, containers, isActive }: TerminalTabProps) {
|
|
15
|
+
export function TerminalTab({ namespace, podName, containerName, containers, isActive, shell, note }: TerminalTabProps) {
|
|
14
16
|
const createSession = (container: string) =>
|
|
15
17
|
Promise.resolve({
|
|
16
|
-
wsUrl: getWsUrl(`/pods/${namespace}/${podName}/exec?container=${container}`),
|
|
18
|
+
wsUrl: getWsUrl(`/pods/${namespace}/${podName}/exec?container=${container}${shell ? `&shell=${encodeURIComponent(shell)}` : ''}`),
|
|
17
19
|
})
|
|
18
20
|
|
|
19
21
|
const createDebugContainer = async (targetContainer: string) => {
|
|
@@ -37,7 +39,9 @@ export function TerminalTab({ namespace, podName, containerName, containers, isA
|
|
|
37
39
|
containers={containers}
|
|
38
40
|
isActive={isActive}
|
|
39
41
|
createSession={createSession}
|
|
40
|
-
createDebugContainer={createDebugContainer}
|
|
42
|
+
createDebugContainer={shell ? undefined : createDebugContainer}
|
|
43
|
+
note={note}
|
|
44
|
+
command={shell}
|
|
41
45
|
/>
|
|
42
46
|
)
|
|
43
47
|
}
|
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
import { useTrafficFlowList } from '../traffic/TrafficFlowListContext'
|
|
2
2
|
import { TrafficFlowList } from '../traffic/TrafficFlowList'
|
|
3
|
-
import { List } from 'lucide-react'
|
|
3
|
+
import { List, Loader2 } from 'lucide-react'
|
|
4
4
|
|
|
5
5
|
export function TrafficFlowListTab() {
|
|
6
|
-
const { flows, responsesCallerOriented } = useTrafficFlowList()
|
|
6
|
+
const { flows, responsesCallerOriented, graphSelection, loading, note } = useTrafficFlowList()
|
|
7
7
|
|
|
8
|
-
if (
|
|
8
|
+
if (loading) {
|
|
9
9
|
return (
|
|
10
10
|
<div className="flex items-center justify-center h-full text-sm text-theme-text-tertiary gap-2">
|
|
11
|
-
<
|
|
12
|
-
|
|
11
|
+
<Loader2 className="w-4 h-4 animate-spin" />
|
|
12
|
+
Loading flows for the selection…
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (flows.length === 0) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex flex-col items-center justify-center h-full gap-1 px-4 text-center">
|
|
20
|
+
<div className="flex items-center gap-2 text-sm text-theme-text-tertiary">
|
|
21
|
+
<List className="w-4 h-4" />
|
|
22
|
+
{graphSelection ? 'No flow records for this selection' : 'Navigate to Traffic view to see flows'}
|
|
23
|
+
</div>
|
|
24
|
+
{note && <div className="text-[11px] text-theme-text-tertiary">{note}</div>}
|
|
13
25
|
</div>
|
|
14
26
|
)
|
|
15
27
|
}
|
|
16
28
|
|
|
17
|
-
return
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex flex-col h-full">
|
|
31
|
+
{note && (
|
|
32
|
+
<div className="px-3 py-1 border-b border-theme-border text-[11px] text-theme-text-tertiary">{note}</div>
|
|
33
|
+
)}
|
|
34
|
+
<div className="relative flex-1 min-h-0">
|
|
35
|
+
<TrafficFlowList flows={flows} responsesCallerOriented={responsesCallerOriented} />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
18
39
|
}
|
|
@@ -389,10 +389,10 @@ export function BatchExecutionFullscreen({ kind, apiKind, namespace, name, resou
|
|
|
389
389
|
</div>
|
|
390
390
|
<div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs text-theme-text-tertiary">
|
|
391
391
|
<span>{RUN_KIND_LABEL[selectedRun.kind] ?? selectedRun.kind}</span>
|
|
392
|
-
{selectedRun.startedAt && <span>started {
|
|
392
|
+
{selectedRun.startedAt && <span>started {formatTimeAgo(selectedRun.startedAt)}</span>}
|
|
393
393
|
{selectedRun.trigger === 'manual' && <span>manual trigger</span>}
|
|
394
394
|
{selectedRun.trigger === 'event' && <span>event triggered</span>}
|
|
395
|
-
{selectedRun.scheduledAt && <span>scheduled {
|
|
395
|
+
{selectedRun.scheduledAt && <span>scheduled {formatTimeAgo(selectedRun.scheduledAt)}</span>}
|
|
396
396
|
{selectedResourceQuery.isFetching && <span>refreshing</span>}
|
|
397
397
|
</div>
|
|
398
398
|
</div>
|
|
@@ -568,8 +568,8 @@ function sourceFacts(kind: string, resource: any, runs: WorkloadRun[]) {
|
|
|
568
568
|
duration: latest ? formatRunDuration(latest) : '',
|
|
569
569
|
work: `${status.active?.length ?? 0} active`,
|
|
570
570
|
facts: [
|
|
571
|
-
['Last schedule', status.lastScheduleTime ?
|
|
572
|
-
['Last success', status.lastSuccessfulTime ?
|
|
571
|
+
['Last schedule', status.lastScheduleTime ? formatTimeAgo(status.lastScheduleTime) : 'Never'],
|
|
572
|
+
['Last success', status.lastSuccessfulTime ? formatTimeAgo(status.lastSuccessfulTime) : 'Never'],
|
|
573
573
|
['Starting deadline', spec.startingDeadlineSeconds ? `${spec.startingDeadlineSeconds}s` : 'None'],
|
|
574
574
|
],
|
|
575
575
|
definition,
|
|
@@ -818,15 +818,15 @@ function RunDetailList({ run, resource, workflowExecution, scheduledParent, memb
|
|
|
818
818
|
...(run.jobset?.replicatedJob ? [['Role', run.jobset?.replicatedJob]] as Array<[string, string]> : []),
|
|
819
819
|
...(run.jobset?.restartAttempt ? [['JobSet restart attempt', `#${run.jobset?.restartAttempt}`]] as Array<[string, string]> : []),
|
|
820
820
|
...(run.jobset?.jobRestartAttempt ? [['Job restart attempt', `#${run.jobset?.jobRestartAttempt}`]] as Array<[string, string]> : []),
|
|
821
|
-
['Started', run.startedAt ?
|
|
822
|
-
['Finished', run.finishedAt ?
|
|
821
|
+
['Started', run.startedAt ? formatTimeAgo(run.startedAt) : '-'],
|
|
822
|
+
['Finished', run.finishedAt ? formatTimeAgo(run.finishedAt) : run.active ? 'Running' : '-'],
|
|
823
823
|
['Duration', formatRunDuration(run) || '-'],
|
|
824
824
|
...(run.progress ? [['Progress', run.progress]] as Array<[string, string]> : []),
|
|
825
825
|
...(!memberCollection && (scheduledParent || run.trigger || run.scheduledAt) ? [
|
|
826
826
|
['Trigger', run.trigger === 'manual' ? 'Manual' : run.trigger === 'event' ? 'Event' : run.scheduledAt ? 'Cron schedule' : '-'],
|
|
827
827
|
] as Array<[string, string]> : []),
|
|
828
828
|
...(run.scheduledAt ? [
|
|
829
|
-
['Cron scheduled',
|
|
829
|
+
['Cron scheduled', formatTimeAgo(run.scheduledAt)],
|
|
830
830
|
['Start delay', formatScheduleDelay(run) || '-'],
|
|
831
831
|
] as Array<[string, string]> : []),
|
|
832
832
|
['Parallelism', run.parallelism ? String(run.parallelism) : '-'],
|
|
@@ -1008,7 +1008,7 @@ function RunActivityRow({ item, runMessage }: { item: WorkflowExecutionActivity;
|
|
|
1008
1008
|
<div className="min-w-0 flex-1">
|
|
1009
1009
|
<div className="flex items-start justify-between gap-3">
|
|
1010
1010
|
<div className="truncate text-sm font-medium text-theme-text-primary">{item.label}</div>
|
|
1011
|
-
<div className="shrink-0 text-xs text-theme-text-tertiary">{
|
|
1011
|
+
<div className="shrink-0 text-xs text-theme-text-tertiary">{formatTimeAgo(item.at)}</div>
|
|
1012
1012
|
</div>
|
|
1013
1013
|
{item.detail && item.detail !== runMessage && item.tone !== 'danger' && <div className="mt-0.5 break-words text-xs text-theme-text-secondary">{item.detail}</div>}
|
|
1014
1014
|
</div>
|
|
@@ -1539,10 +1539,10 @@ function formatRunDuration(run: WorkloadRun): string {
|
|
|
1539
1539
|
|
|
1540
1540
|
function formatRunTime(run: WorkloadRun): string {
|
|
1541
1541
|
const raw = run.startedAt || run.scheduledAt || run.finishedAt
|
|
1542
|
-
return raw ?
|
|
1542
|
+
return raw ? formatTimeAgo(raw) : ''
|
|
1543
1543
|
}
|
|
1544
1544
|
|
|
1545
|
-
function
|
|
1545
|
+
function formatTimeAgo(value: string): string {
|
|
1546
1546
|
const t = Date.parse(value)
|
|
1547
1547
|
if (Number.isNaN(t)) return value
|
|
1548
1548
|
const diff = Date.now() - t
|
|
@@ -1577,7 +1577,7 @@ function emptyRunsCopy(kind: string, resource: any): { headline: string; body: s
|
|
|
1577
1577
|
}
|
|
1578
1578
|
const lastScheduled = resource?.status?.lastScheduleTime || resource?.status?.lastScheduledTime
|
|
1579
1579
|
if (lastScheduled) {
|
|
1580
|
-
return { headline: 'No retained runs', body: `This resource last scheduled work ${
|
|
1580
|
+
return { headline: 'No retained runs', body: `This resource last scheduled work ${formatTimeAgo(lastScheduled)}, but its ${runKindPluralForSchedule(kind)} are no longer retained in Kubernetes.` }
|
|
1581
1581
|
}
|
|
1582
1582
|
return { headline: 'No runs yet', body: `Kubernetes does not currently have retained ${runKindPluralForSchedule(kind)} for this resource.` }
|
|
1583
1583
|
}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { ArgoResourceDiff } from '@skyhook-io/k8s-ui'
|
|
2
|
-
import type { GitOpsInsightRef } from '@skyhook-io/k8s-ui'
|
|
2
|
+
import type { GitOpsDrift, GitOpsInsightRef } from '@skyhook-io/k8s-ui'
|
|
3
3
|
import { useArgoResourceDiff } from '../../api/client'
|
|
4
4
|
|
|
5
5
|
interface ArgoResourceDiffLoaderProps {
|
|
6
6
|
appNamespace: string
|
|
7
7
|
appName: string
|
|
8
8
|
resourceRef: GitOpsInsightRef
|
|
9
|
+
fallbackDrift?: GitOpsDrift
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
// Host wrapper: fetches the Argo CD resource diff and hands the data to the
|
|
12
13
|
// pure k8s-ui presentation component. Mirrors the data-in-web / render-in-
|
|
13
14
|
// k8s-ui split the resource renderers use for their host-wired data.
|
|
14
|
-
export function ArgoResourceDiffLoader({ appNamespace, appName, resourceRef }: ArgoResourceDiffLoaderProps) {
|
|
15
|
+
export function ArgoResourceDiffLoader({ appNamespace, appName, resourceRef, fallbackDrift }: ArgoResourceDiffLoaderProps) {
|
|
15
16
|
const { data, isLoading, error } = useArgoResourceDiff(appNamespace, appName, resourceRef)
|
|
16
17
|
return (
|
|
17
18
|
<ArgoResourceDiff
|
|
18
19
|
diff={data ?? null}
|
|
19
20
|
loading={isLoading}
|
|
20
21
|
error={(error as Error | null)?.message ?? null}
|
|
22
|
+
fallbackDrift={fallbackDrift}
|
|
21
23
|
/>
|
|
22
24
|
)
|
|
23
25
|
}
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
isArgoOperationInProgress,
|
|
26
26
|
isArgoSuspendedByRadar,
|
|
27
27
|
gitOpsInsightChangeKey,
|
|
28
|
-
initNavigationMap,
|
|
29
28
|
kindToPluralWithGroup,
|
|
30
29
|
normalizeArgoApplication,
|
|
31
30
|
normalizeArgoApplicationSet,
|
|
@@ -72,7 +71,7 @@ import {
|
|
|
72
71
|
useGitOpsTree,
|
|
73
72
|
useResource,
|
|
74
73
|
} from '../../api/client'
|
|
75
|
-
import { useAPIResources } from '../../api/apiResources'
|
|
74
|
+
import { useAPIResources, GITOPS_KINDS } from '../../api/apiResources'
|
|
76
75
|
import { useConnection } from '../../context/ConnectionContext'
|
|
77
76
|
import { apiUrl, getAuthHeaders, getCredentialsMode } from '../../api/config'
|
|
78
77
|
import { useRegisterShortcut } from '../../hooks/useKeyboardShortcuts'
|
|
@@ -84,17 +83,6 @@ import { RemoteDestinationCloudHint } from './RemoteDestinationCloudHint'
|
|
|
84
83
|
const GITOPS_HEALTH_DOCS_URL = 'https://radarhq.io/docs/features/gitops#per-resource-health'
|
|
85
84
|
import type { GitOpsHistoryItem } from '@skyhook-io/k8s-ui'
|
|
86
85
|
|
|
87
|
-
const GITOPS_KINDS: APIResource[] = [
|
|
88
|
-
{ name: 'applications', kind: 'Application', group: 'argoproj.io', version: 'v1alpha1', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
89
|
-
{ name: 'applicationsets', kind: 'ApplicationSet', group: 'argoproj.io', version: 'v1alpha1', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
90
|
-
{ name: 'appprojects', kind: 'AppProject', group: 'argoproj.io', version: 'v1alpha1', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
91
|
-
{ name: 'kustomizations', kind: 'Kustomization', group: 'kustomize.toolkit.fluxcd.io', version: 'v1', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
92
|
-
{ name: 'helmreleases', kind: 'HelmRelease', group: 'helm.toolkit.fluxcd.io', version: 'v2', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
93
|
-
{ name: 'gitrepositories', kind: 'GitRepository', group: 'source.toolkit.fluxcd.io', version: 'v1', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
94
|
-
{ name: 'ocirepositories', kind: 'OCIRepository', group: 'source.toolkit.fluxcd.io', version: 'v1beta2', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
95
|
-
{ name: 'helmrepositories', kind: 'HelmRepository', group: 'source.toolkit.fluxcd.io', version: 'v1', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
96
|
-
{ name: 'alerts', kind: 'Alert', group: 'notification.toolkit.fluxcd.io', version: 'v1beta3', namespaced: true, verbs: ['list', 'get'], isCrd: true },
|
|
97
|
-
]
|
|
98
86
|
|
|
99
87
|
type ArgoSyncDialogTarget =
|
|
100
88
|
| { scope: 'application' }
|
|
@@ -164,9 +152,6 @@ function GitOpsTableView({ namespaces, onClearNamespaces }: { namespaces: string
|
|
|
164
152
|
})
|
|
165
153
|
}
|
|
166
154
|
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
initNavigationMap([...(apiResources ?? []), ...GITOPS_KINDS])
|
|
169
|
-
}, [apiResources])
|
|
170
155
|
|
|
171
156
|
const hasGitOpsRowResource = useMemo(() => (
|
|
172
157
|
hasAPIResource(apiResources, 'applications', 'argoproj.io') ||
|
|
@@ -816,8 +801,8 @@ function GitOpsDetailView({ namespaces, onOpenResource, onOpenSettings }: GitOps
|
|
|
816
801
|
) : undefined}
|
|
817
802
|
focusKey={changesFocusKey}
|
|
818
803
|
tree={tree}
|
|
819
|
-
renderResourceDiff={isArgoApp ? (ref) => (
|
|
820
|
-
<ArgoResourceDiffLoader appNamespace={namespace} appName={name} resourceRef={ref} />
|
|
804
|
+
renderResourceDiff={isArgoApp ? (ref, fallbackDrift) => (
|
|
805
|
+
<ArgoResourceDiffLoader appNamespace={namespace} appName={name} resourceRef={ref} fallbackDrift={fallbackDrift} />
|
|
821
806
|
) : undefined}
|
|
822
807
|
onOpenSettings={onOpenSettings}
|
|
823
808
|
settingsNote={settingsNote}
|
|
@@ -1097,18 +1082,26 @@ function newestConditionTime(resource: any): string {
|
|
|
1097
1082
|
// many of them are healthy / drifted" at a glance, without making the user
|
|
1098
1083
|
// count facets in the filter rail.
|
|
1099
1084
|
function TopologyCounts({ tree }: { tree: GitOpsResourceTree }) {
|
|
1100
|
-
const
|
|
1085
|
+
const counted = (tree.nodes ?? []).filter((n) => n.role !== 'group' && n.role !== 'root')
|
|
1086
|
+
// Dependencies (Flux sources, charts, dependsOn targets) are consumed, not
|
|
1087
|
+
// managed: they get their own count and never inflate the managed totals.
|
|
1088
|
+
const nodes = counted.filter((n) => !n.dependency)
|
|
1089
|
+
const dependencies = counted.length - nodes.length
|
|
1101
1090
|
const total = nodes.length
|
|
1102
|
-
if (
|
|
1103
|
-
const
|
|
1104
|
-
const degraded = nodes.filter((n) => {
|
|
1091
|
+
if (counted.length === 0) return null
|
|
1092
|
+
const isDegraded = (n: GitOpsTreeNode) => {
|
|
1105
1093
|
const h = (n.health || '').toLowerCase()
|
|
1106
1094
|
return h === 'degraded' || h === 'missing' || h === 'unhealthy'
|
|
1107
|
-
}
|
|
1095
|
+
}
|
|
1096
|
+
const healthy = nodes.filter((n) => (n.health || '').toLowerCase() === 'healthy').length
|
|
1097
|
+
const degraded = nodes.filter(isDegraded).length
|
|
1098
|
+
const failingDependencies = counted.filter((n) => n.dependency && isDegraded(n)).length
|
|
1108
1099
|
const outOfSync = nodes.filter((n) => (n.sync || '').toLowerCase() === 'outofsync').length
|
|
1109
1100
|
return (
|
|
1110
1101
|
<div className="hidden min-w-0 flex-1 items-center gap-3 truncate text-[11px] text-theme-text-tertiary sm:flex">
|
|
1111
1102
|
<span><span className="text-theme-text-primary">{total}</span> resources</span>
|
|
1103
|
+
{dependencies > 0 && <span><span className="text-theme-text-primary">{dependencies}</span> {dependencies === 1 ? 'dependency' : 'dependencies'}</span>}
|
|
1104
|
+
{failingDependencies > 0 && <span className="flex items-center gap-1 font-medium text-red-600 dark:text-red-400"><span className="h-1.5 w-1.5 rounded-full bg-red-500" /> {failingDependencies} {failingDependencies === 1 ? 'dependency' : 'dependencies'} failing</span>}
|
|
1112
1105
|
{healthy > 0 && <span className="flex items-center gap-1"><span className="h-1.5 w-1.5 rounded-full bg-emerald-500" /> {healthy} healthy</span>}
|
|
1113
1106
|
{/* Bad-news counts use status colors on the number itself so the worst
|
|
1114
1107
|
fact in the row visually pops, not just the dot next to it. */}
|
|
@@ -5,10 +5,10 @@ import { clsx } from 'clsx'
|
|
|
5
5
|
import { useHelmRepositories, useSearchCharts, useUpdateRepository, useUpdateRepositorySilent, useArtifactHubSearch, type ArtifactHubSortOption } from '../../api/client'
|
|
6
6
|
import { useCanHelmWrite, useIsAuthEnabled } from '../../contexts/CapabilitiesContext'
|
|
7
7
|
import type { ChartInfo, HelmRepository, ArtifactHubChart, ChartSource } from '../../types'
|
|
8
|
-
import { formatAge } from './helm-utils'
|
|
9
8
|
import { SEVERITY_BADGE } from '../../utils/badge-colors'
|
|
10
9
|
import { Tooltip } from '../ui/Tooltip'
|
|
11
10
|
import { useToast } from '../ui/Toast'
|
|
11
|
+
import { formatRelativeAgeTime } from '@skyhook-io/k8s-ui/utils/format'
|
|
12
12
|
|
|
13
13
|
interface ChartBrowserProps {
|
|
14
14
|
onChartSelect: (repo: string, chart: string, version: string, source: ChartSource) => void
|
|
@@ -431,7 +431,7 @@ function RepoDropdownItem({ repo, isSelected, onSelect, onUpdate, isUpdating, ca
|
|
|
431
431
|
{repo.name}
|
|
432
432
|
{repo.lastUpdated && (
|
|
433
433
|
<span className="text-xs text-theme-text-tertiary ml-2">
|
|
434
|
-
{
|
|
434
|
+
{formatRelativeAgeTime(repo.lastUpdated)}
|
|
435
435
|
</span>
|
|
436
436
|
)}
|
|
437
437
|
</button>
|
|
@@ -514,7 +514,7 @@ interface ArtifactHubChartCardProps {
|
|
|
514
514
|
function ArtifactHubChartCard({ chart, onSelect }: ArtifactHubChartCardProps) {
|
|
515
515
|
// Format Unix timestamp to relative age
|
|
516
516
|
const lastUpdated = chart.updatedAt
|
|
517
|
-
?
|
|
517
|
+
? formatRelativeAgeTime(new Date(chart.updatedAt * 1000).toISOString())
|
|
518
518
|
: null
|
|
519
519
|
|
|
520
520
|
return (
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import { CompareSummary } from './HelmCompareRoute'
|
|
5
|
+
|
|
6
|
+
const sameDiff = '--- revision-1\n+++ revision-2\n'
|
|
7
|
+
const changedDiff = '--- revision-1\n+++ revision-2\n@@ -1 +1 @@\n-old\n+new\n'
|
|
8
|
+
|
|
9
|
+
function renderSummary(overridesDiff: string, effectiveValuesDiff: string) {
|
|
10
|
+
document.body.innerHTML = renderToStaticMarkup(
|
|
11
|
+
<CompareSummary
|
|
12
|
+
revision1={1}
|
|
13
|
+
revision2={2}
|
|
14
|
+
manifestDiff={sameDiff}
|
|
15
|
+
manifestLoading={false}
|
|
16
|
+
manifestError={null}
|
|
17
|
+
overridesDiff={overridesDiff}
|
|
18
|
+
overridesLoading={false}
|
|
19
|
+
overridesError={null}
|
|
20
|
+
effectiveValuesDiff={effectiveValuesDiff}
|
|
21
|
+
effectiveValuesLoading={false}
|
|
22
|
+
effectiveValuesError={null}
|
|
23
|
+
notesDiff={sameDiff}
|
|
24
|
+
notesLoading={false}
|
|
25
|
+
notesError={null}
|
|
26
|
+
hooksLoading={false}
|
|
27
|
+
hooksError={null}
|
|
28
|
+
resourceLoading={false}
|
|
29
|
+
resourceError={null}
|
|
30
|
+
/>,
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function signalText(section: string) {
|
|
35
|
+
return document.querySelector(`a[href="#${section}"]`)?.textContent
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('CompareSummary value signals', () => {
|
|
39
|
+
it('shows unchanged overrides and changed effective values when chart defaults change', () => {
|
|
40
|
+
renderSummary(sameDiff, changedDiff)
|
|
41
|
+
|
|
42
|
+
expect(signalText('overrides')).toBe('Overridessame')
|
|
43
|
+
expect(signalText('effective-values')).toBe('Effective values1 add / 1 remove')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('shows both signals changed when an override changes effective values', () => {
|
|
47
|
+
renderSummary(changedDiff, changedDiff)
|
|
48
|
+
|
|
49
|
+
expect(signalText('overrides')).toBe('Overrides1 add / 1 remove')
|
|
50
|
+
expect(signalText('effective-values')).toBe('Effective values1 add / 1 remove')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('shows both signals unchanged when both value sets are identical', () => {
|
|
54
|
+
renderSummary(sameDiff, sameDiff)
|
|
55
|
+
|
|
56
|
+
expect(signalText('overrides')).toBe('Overridessame')
|
|
57
|
+
expect(signalText('effective-values')).toBe('Effective valuessame')
|
|
58
|
+
})
|
|
59
|
+
})
|